Imports System.Collections
Imports System.Collections.Specialized
Imports System.Drawing
Imports System.Drawing.Drawing2D
Imports System.Windows
Imports CommonNamespace
Imports Microsoft.CSharp

Public Class AnnotatedImage
    'Class that permits rendering of an image and overlayed annotations
    'It will size itself and parent controls must implement the Resize event
    'to adjust their size accordingly ...
    Inherits System.Windows.Forms.Control

    Public PassKey As String = ""

#Region "Declarations"

#Region "Variables"

    'Bitmap variables, mirrored by a property
    Private oBitmap As Bitmap, oMasks As New MaskCollection
    Private objMaximimumSize As New System.Drawing.Size(800, 600), iWindowSize As Integer
    Private objMousePos As New Point(0, 0), bDirty, bUpdating As Boolean
    Private sDisplayZoom As Single, ptDisplayOffset As Point, oImageURI, oAnnotationsURI As Uri
    Private oSelectedItem As Object

    'Private WithEvents oAnnotations As Annotations
    Private WithEvents oAnnotationContainers As New AnnotationContainerCollection(Me)
    Private WithEvents oAnnotationsManager As AnnotationsManager

    'Private variables for interactive creation of annotations
    Private bCreatingAnnotation As Boolean
    Private strCreatedAnnotationKey, strCreatedAnnotationAnnotationContainerKey As String

    'Context sensitive menus on the canvas. These are similar but not equal to the
    'menus in the Annotation Manager
    Private oAnnotationMenu As ContextMenu
    Private WithEvents oAnnotationMenuItemDelete As MenuItem
    Private WithEvents oAnnotationMenuItemProperties As MenuItem
    Private WithEvents oAnnotationMenuItemDeleteContainer As MenuItem
    Private WithEvents oAnnotationMenuItemPlotBack As MenuItem
    Private WithEvents oAnnotationMenuItemAdd As MenuItem

    Private oCreatingAnnotationMenu As ContextMenu
    Private WithEvents oCreatingAnnotationMenuItemDone As MenuItem
    Private WithEvents oCreatingAnnotationMenuItemCancel As MenuItem
    Private WithEvents oCreatingAnnotationMenuItemUndo As MenuItem
#End Region

#Region "Constants and enumerations"
    'The screen resolution 
    Private Const SCREEN_DPI As Integer = 96

    Const CREATED_ANNOTATION_MIN_DISTANCE As Integer = 2

    'Mode defining how individual annotations in a AnnotationContainer are added together 
    'to create a region
    Public Enum RegionAddMode
        Union = 0
        Intersect
        XorMode 'Default
        Complement
    End Enum

    Enum AnnotationManagerMenus
        Annotation = 1
        AnnotationNew = 2
        AnnotationContainer = 3
    End Enum

#End Region

#Region "Events"
    'Public events
    Public Event DisplayZoomChanged(ByVal sDisplayZoom As Single)
    Public Event PixelAveragingWindowSizeChanged(ByVal iWindowSize As Integer)
    Public Event BitmapChanged(ByVal oBitmap As Bitmap) 'Occurs when a new bitmap is set
    Public Event BitmapDataChanged(ByVal oBitmap As Bitmap) 'Occurs when we change the data of a bitmap...
    Public Event RegionDrawn(ByVal ChangedRegion As Region)
    'Public events
    Public Event AnnotationCreationDone(ByVal oAnnotations As Annotation)
    Public Event AnnotationCreationCanceled(ByVal oAnnotations As Annotation)
    Public Event AnnotationCreationPointAdded(ByVal oAnnotations As Annotation)
    Public Event AnnotationCreationPointRemoved(ByVal oAnnotations As Annotation)
    Public Event AnnotationCreationMouseMove(ByVal e As System.Windows.Forms.MouseEventArgs, ByVal oAnnotations As Annotation)
    Public Event AnnotationItemSelected(ByVal obj As Object)
    Public Event AnnotationHover(ByVal e As System.Windows.Forms.MouseEventArgs, ByVal oAnnotations As Annotation)
    Public Event AnnotationsChanged(ByVal sender As Object)
    Public Event AnnotationsManagerVisibleChanged(ByVal bTrue As Boolean)  'Visibility of annotation manager has changed
#End Region

#Region "Collection classes"
    'Collection classes for bitmasks drawn in overlay over the image
    Public Class MaskCollection
        Inherits NameObjectCollectionBase

        Public Sub New()
        End Sub 'New

        Default Public ReadOnly Property Item(ByVal key As String) As Bitmap
            Get
                'Return nothing if key not present!
                Return CType(Me.BaseGet(key), Bitmap)
            End Get
        End Property

        Default Public ReadOnly Property Item(ByVal index As Integer) As Bitmap
            Get
                Return CType(Me.BaseGet(index), Bitmap)
            End Get
        End Property

        Public Function KeyExists(ByVal key As String) As Boolean
            If Me.Item(key) Is Nothing Then
                Return False
            Else
                Return True
            End If
        End Function

        Public Function ItemExists(ByVal objAnn As Bitmap) As Boolean
            Dim i As Integer
            For i = 0 To Me.Count - 1
                If Object.Equals(Me.Item(i), objAnn) Then
                    Return True
                End If
            Next
            Return False
        End Function

        Public Sub Add(ByVal value As Bitmap, ByVal strKey As String)
            'Key must be unique, so we remove existing entry
            Me.BaseAdd(strKey, value)
        End Sub

        Public Overloads Sub Remove(ByVal key As String)
            Me.BaseRemove(key)
        End Sub

        Public Overloads Sub Remove(ByVal index As Integer)
            Me.BaseRemoveAt(index)
        End Sub

        Public Sub Clear()
            Me.BaseClear()
        End Sub

        Public Shadows Function GetEnumerator() As MaskCollectionEnumerator
            Return New MaskCollectionEnumerator(Me)
        End Function

        Public Class MaskCollectionEnumerator
            ' This overriding of IEnumerator allows for each to be used
            Implements IEnumerator

            Private objMaskCollection As MaskCollection
            Private index As Integer = -1

            Public Sub New(ByVal objMaskCollection As MaskCollection)
                Me.objMaskCollection = objMaskCollection
            End Sub

            Public ReadOnly Property Current() As Object Implements IEnumerator.Current
                Get
                    Return CType(Me.objMaskCollection.Item(index), Bitmap)
                End Get
            End Property

            Public Function MoveNext() As Boolean Implements IEnumerator.MoveNext
                If index < objMaskCollection.Count - 1 Then
                    index += 1
                    Return True
                Else
                    Return False
                End If
            End Function

            Public Sub Reset() Implements IEnumerator.Reset
                index = -1
            End Sub
        End Class
    End Class

    'Strongly typed  collection classes for AnnotationContainers
    'Because of the strong typing we need to implement the 
    'ienumerable interface too or very strange errors occur when trying 
    'to use for each constructs!
    Public Class AnnotationContainerCollection
        Inherits NameObjectCollectionBase

        Private oAnnotatedImage As AnnotatedImage
        Public Event Changed(ByVal sender As Object)

        Public Sub RaiseChangedEvent(ByVal sender As Object)
            'Pass it on as a collection event
            RaiseEvent Changed(sender)
        End Sub

        Public Sub New(ByVal oAnnotatedImage As AnnotatedImage)
            Me.oAnnotatedImage = oAnnotatedImage
        End Sub 'New

        Default Public ReadOnly Property Item(ByVal key As String) As AnnotationContainer
            Get
                Return CType(Me.BaseGet(key), AnnotationContainer)
            End Get
        End Property

        Default Public ReadOnly Property Item(ByVal index As Integer) As AnnotationContainer
            Get
                Return CType(Me.BaseGet(index), AnnotationContainer)
            End Get
        End Property

        Public Sub Add(ByVal value As AnnotationContainer, ByVal key As String)
            'Key must be unique, so we remove existing entry
            Debug.WriteLine("Adding annotation container " & key & " to collection", "AnnotationCollection.Add")
            If Me.KeyExists(key) Then Me.BaseRemove(key)

            value.strKey = key
            value.oAnnotatedImage = Me.oAnnotatedImage

            'Event handlers on the items
            AddHandler value.Changed, AddressOf RaiseChangedEvent

            Me.BaseAdd(value.Key, value)
            RaiseEvent Changed(Nothing)
        End Sub

        Public Function KeyExists(ByVal key As String) As Boolean
            If Me.Item(key) Is Nothing Then
                Return False
            Else
                Return True
            End If
        End Function

        Public Overloads Sub Remove(ByVal key As String)
            Me.BaseRemove(key)
            RaiseEvent Changed(Nothing)
        End Sub

        Public Overloads Sub Remove(ByVal index As Integer)
            Me.BaseRemoveAt(index)
            RaiseEvent Changed(Nothing)
        End Sub

        Public Sub Clear()
            Me.BaseClear()
            RaiseEvent Changed(Nothing)
        End Sub

        Public Shadows Function GetEnumerator() As AnnotationContainerCollectionEnumerator
            Return New AnnotationContainerCollectionEnumerator(Me)
        End Function

        Public Class AnnotationContainerCollectionEnumerator
            ' This overriding of IEnumerator allows for each to be used
            Implements IEnumerator

            Private objAnnotationContainerCollection As AnnotationContainerCollection
            Private index As Integer = -1

            Public Sub New(ByVal objAnnotationContainerCollection As AnnotationContainerCollection)
                Me.objAnnotationContainerCollection = objAnnotationContainerCollection
            End Sub

            Public ReadOnly Property Current() As Object Implements IEnumerator.Current
                Get
                    Return CType(Me.objAnnotationContainerCollection.Item(index), AnnotationContainer)
                End Get
            End Property

            Public Function MoveNext() As Boolean Implements IEnumerator.MoveNext
                If index < objAnnotationContainerCollection.Count - 1 Then
                    index += 1
                    Return True
                Else
                    Return False
                End If
            End Function

            Public Sub Reset() Implements IEnumerator.Reset
                index = -1
            End Sub
        End Class

    End Class

    'Strongly typed collection classes for AnnotationContainers and annotations
    Public Class AnnotationCollection
        Inherits NameObjectCollectionBase

        Private oAnnotationContainer As AnnotationContainer
        Public Event Changed(ByVal sender As Object)

        Public Sub RaiseChangedEvent(ByVal sender As Object)
            'Pass it on as a collection event
            RaiseEvent Changed(sender)
        End Sub

        Public Sub New(ByVal oAnnotationContainer As AnnotationContainer)
            Me.oAnnotationContainer = oAnnotationContainer
        End Sub 'New

        Default Public ReadOnly Property Item(ByVal key As String) As Annotation
            Get
                'Return nothing if key not present!
                Return CType(Me.BaseGet(key), Annotation)
            End Get
        End Property

        Default Public ReadOnly Property Item(ByVal index As Integer) As Annotation
            Get
                Return CType(Me.BaseGet(index), Annotation)
            End Get
        End Property

        Public Function KeyExists(ByVal key As String) As Boolean
            If Me.Item(key) Is Nothing Then
                Return False
            Else
                Return True
            End If
        End Function

        Public Function ItemExists(ByVal objAnn As Annotation) As Boolean
            Dim i As Integer
            For i = 0 To Me.Count - 1
                If Object.Equals(Me.Item(i), objAnn) Then
                    Return True
                End If
            Next
            Return False
        End Function

        Public Sub Add(ByVal value As Annotation, ByVal strKey As String)
            'Key must be unique, so we remove existing entry
            Debug.WriteLine("Adding annotation " & strKey & " to container " & Me.oAnnotationContainer.Key, "AnnotationContainer.Add")

            If Me.KeyExists(strKey) Then Me.BaseRemove(strKey)
            AddHandler value.Changed, AddressOf RaiseChangedEvent

            Me.BaseAdd(strKey, value)

            value.oAnnotationContainer = Me.oAnnotationContainer
            value.strKey = strKey

            'Default color is that of container, if not set. 
            If value.Color.ToKnownColor = KnownColor.White Then
                value.Color = Me.oAnnotationContainer.Color
            End If

            RaiseEvent Changed(Nothing)
        End Sub

        Public Overloads Sub Remove(ByVal key As String)
            Me.BaseRemove(key)
            RaiseEvent Changed(Nothing)
        End Sub

        Public Overloads Sub Remove(ByVal index As Integer)
            Me.BaseRemoveAt(index)
            RaiseEvent Changed(Nothing)
        End Sub

        Public Sub Clear()
            Me.BaseClear()
            RaiseEvent Changed(Nothing)
        End Sub

        Public Shadows Function GetEnumerator() As AnnotationCollectionEnumerator
            Return New AnnotationCollectionEnumerator(Me)
        End Function

        Public Class AnnotationCollectionEnumerator
            ' This overriding of IEnumerator allows for each to be used
            Implements IEnumerator

            Private objAnnotationCollection As AnnotationCollection
            Private index As Integer = -1

            Public Sub New(ByVal objAnnotationCollection As AnnotationCollection)
                Me.objAnnotationCollection = objAnnotationCollection
            End Sub

            Public ReadOnly Property Current() As Object Implements IEnumerator.Current
                Get
                    Return CType(Me.objAnnotationCollection.Item(index), Annotation)
                End Get
            End Property

            Public Function MoveNext() As Boolean Implements IEnumerator.MoveNext
                If index < objAnnotationCollection.Count - 1 Then
                    index += 1
                    Return True
                Else
                    Return False
                End If
            End Function

            Public Sub Reset() Implements IEnumerator.Reset
                index = -1
            End Sub
        End Class

    End Class

    'Strongly typed Arraylist classes of points for point based annotation
    Public Class AnnotationPoints
        'Strongly typed arraylist. We just ovveride the add methods so only
        'points can be added
        Inherits ArrayList

        Public Event Changed()

        Public Shadows Sub Add(ByVal value As Point)
            MyBase.Add(value)
            RaiseEvent Changed()
        End Sub

        Public Sub AddArray(ByVal value() As Point)
            Dim oPt As Point
            For Each oPt In value
                MyBase.Add(oPt)
            Next
            RaiseEvent Changed()
        End Sub

        Public Shadows Sub AddRange(ByVal value As AnnotationPoints)
            MyBase.AddRange(value)
            RaiseEvent Changed()
        End Sub

        Public Shadows Sub Insert(ByVal index As Integer, ByVal value As Point)
            MyBase.Add(value)
            RaiseEvent Changed()
        End Sub

        Public Shadows Sub InsertRange(ByVal index As Integer, ByVal value As AnnotationPoints)
            MyBase.InsertRange(index, value)
            RaiseEvent Changed()
        End Sub

        Default Public Shadows ReadOnly Property Item(ByVal index As Integer) As Point
            Get
                Return CType(MyBase.Item(index), Point)
            End Get
        End Property

        Public Shadows Sub RemoveAt(ByVal index As Integer)
            MyBase.RemoveAt(index)
            RaiseEvent Changed()
        End Sub

        Public Shadows Sub Remove(ByVal value As Point)
            MyBase.Remove(value)
            RaiseEvent Changed()
        End Sub

        Public Shadows Sub Clear()
            MyBase.Clear()
            RaiseEvent Changed()
        End Sub

        Public Sub Rectangle(ByVal objRect As Rectangle)
            'Add a rectangle as points to a border annotation. This clears the annotation
            MyBase.Clear()

            Dim pt(3) As Point
            pt(0).X = objRect.X
            pt(0).Y = objRect.Y
            pt(1).X = pt(0).X + objRect.Width
            pt(1).Y = pt(0).Y
            pt(2).X = pt(1).X
            pt(2).Y = pt(1).Y + objRect.Height
            pt(3).X = pt(2).X - objRect.Width
            pt(3).Y = pt(2).Y
            Me.AddArray(pt)
        End Sub

        Public Sub Circle(ByVal oCenter As Point, ByVal iRadius As Integer, ByVal iNrPts As Integer)
            'Add a circle as points to a border annotation. This clears the annotation
            Dim oAngleStep As Single = CSng(Math.PI * 2 / iNrPts), i As Integer
            Dim oPt(iNrPts - 1) As Point
            MyBase.Clear()
            For i = 0 To iNrPts - 1
                oPt(i).X = CInt(oCenter.X + iRadius * Math.Cos(oAngleStep * i))
                oPt(i).Y = CInt(oCenter.Y + iRadius * Math.Sin(oAngleStep * i))
            Next
            Me.AddArray(oPt)
        End Sub

        Public Sub Quantize(ByVal sMinDist As Single)
            'Quantize an annotation by removing points that are too close to each other!
            Dim i As Integer, sDist As Single, bChanged As Boolean = False
            Dim oPt1, oPt2 As Point

            i = 0
            If MyBase.Count > 2 Then
                Do
                    oPt1 = CType(MyBase.Item(i), Point)
                    oPt2 = CType(MyBase.Item(i + 1), Point)

                    sDist = CSng(Math.Sqrt(Math.Pow(oPt1.X - oPt2.X, 2) + Math.Pow(oPt1.Y - oPt2.Y, 2)))

                    If sDist <= sMinDist Then
                        'Debug.WriteLine("Removing point " & i - 1, "Annotation.Quantize")
                        bChanged = True
                        MyBase.Remove(MyBase.Item(i + 1))
                    End If
                    i += 1
                Loop While i < (MyBase.Count - 2)
            End If

            If bChanged Then RaiseEvent Changed()
        End Sub

    End Class

#End Region

#End Region

#Region "Component Designer generated code "

    Public Sub New()
        MyBase.New()

        oAnnotationsManager = New AnnotationsManager(Me)

        ' This call is required by the Component Designer.
        InitializeComponent()

        'Double buffering and smooth drawing
        SetStyle(ControlStyles.UserPaint, True)
        SetStyle(ControlStyles.AllPaintingInWmPaint, True)
        SetStyle(ControlStyles.DoubleBuffer, True)

    End Sub

    'Control overrides dispose to clean up the component list.
    Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
        If disposing Then
            If Not (components Is Nothing) Then
                components.Dispose()
            End If
        End If
        MyBase.Dispose(disposing)
    End Sub

    Protected Overrides Sub Finalize()
        Me.Bitmap = Nothing
    End Sub

    'Required by the Control Designer
    Private components As System.ComponentModel.IContainer

    ' NOTE: The following procedure is required by the Component Designer
    ' It can be modified using the Component Designer.  Do not modify it
    ' using the code editor.
    <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
        '
        'AnnotatedImage
        '

    End Sub
#End Region

#Region "Shared helper methods"

    Public Shared Sub RectangleUnion(ByRef rectDst As Rectangle, ByVal rectSrc As Rectangle)
        'Add rectangle together PROPERLY, not like the buggy .NET which treats empty regions as
        'a rectangle at 0,0!!!!
        If rectDst.IsEmpty Then
            rectDst = rectSrc
        Else
            If Not rectSrc.IsEmpty Then
                rectDst = Rectangle.Union(rectDst, rectSrc)
            End If
        End If
    End Sub

    'Use the transformation matrix of .NET for arrays of points
    Public Overloads Shared Function ImageToClientCoordinates(ByVal objPt As Point, _
                                                              ByVal sZoom As Single, _
                                                              ByVal objOffset As Point) As Point
        'Take the image offset and zoom into account ...
        Return New Point(CInt((objPt.X - objOffset.X) * sZoom), CInt((objPt.Y - objOffset.Y) * sZoom))
    End Function

    Public Overloads Shared Function ImageToClientCoordinates(ByVal objRect As Rectangle, _
                                                            ByVal sZoom As Single, _
                                                            ByVal objOffset As Point) As Rectangle
        'Take the image offset and zoom into account ...
        Return New Rectangle(CInt((objRect.X - objOffset.X) * sZoom), CInt((objRect.Y - objOffset.Y) * sZoom), CInt(objRect.Width * sZoom), CInt(objRect.Height * sZoom))
    End Function

    Public Overloads Shared Function ClientToImageCoordinates(ByVal objPt As Point, _
                                                    ByVal sZoom As Single, _
                                                    ByVal objOffset As Point) As Point
        'Take the image offset and zoom into account ...
        Return New Point(CInt(objPt.X / sZoom + objOffset.X), CInt(objPt.Y / sZoom + objOffset.Y))
    End Function

    Public Overloads Shared Function ClientToImageCoordinates(ByVal objRect As Rectangle, _
                                                    ByVal sZoom As Single, _
                                                    ByVal objOffset As Point) As Rectangle
        'Take the image offset and zoom into account ...
        Return New Rectangle(CInt(objRect.X / sZoom + objOffset.X), CInt(objRect.Y / sZoom + objOffset.Y), CInt(objRect.Width / sZoom), CInt(objRect.Height / sZoom))
    End Function

    Public Overloads Shared Function PointToRectangle(ByVal objPt As Point, ByVal sRectSize As Single) As RectangleF
        'Create an array of rectangles, one for each point in the annotation
        Dim objRect As RectangleF
        With objRect
            .X = objPt.X - sRectSize / 2
            .Y = objPt.Y - sRectSize / 2
            .Width = sRectSize
            .Height = sRectSize
        End With
        Return objRect
    End Function

    Public Overloads Shared Function PointToRectangle(ByVal objPts() As Point, ByVal sRectSize As Single) As RectangleF()
        'Create an array of rectangles, one for each point in the annotation
        Dim i As Integer, objRect(objPts.Length - 1) As RectangleF
        For i = 0 To objPts.Length - 1
            objRect(i) = PointToRectangle(objPts(i), sRectSize)
        Next
        Return objRect
    End Function

    Public Overloads Shared Sub TransformRegion(ByVal oRgn As Region, ByVal sScale As Single)
        Dim objM As New System.Drawing.Drawing2D.Matrix
        objM.Scale(sScale, sScale)
        oRgn.Transform(objM)
    End Sub

    Public Overloads Shared Sub TransformRegion(ByVal oRgn As Region, ByVal sScale As Single, ByVal iOffsetX As Integer, ByVal iOffsetY As Integer)
        Dim objM As New System.Drawing.Drawing2D.Matrix
        objM.Scale(sScale, sScale)
        objM.Translate(iOffsetX, iOffsetY)
        oRgn.Transform(objM)
    End Sub

#End Region

#Region "Properties"

    Public ReadOnly Property AnnotationContainers() As AnnotationContainerCollection
        'Return all annotation AnnotationContainers, and allow operations on them
        Get
            Return oAnnotationContainers
        End Get
    End Property

    Public Property SelectedItem() As Object
        Set(ByVal Value As Object)
            Me.oSelectedItem = Value
            Me.Draw()
            RaiseEvent AnnotationItemSelected(Value)
        End Set
        Get
            'Return the selected item if any
            Return Me.oSelectedItem
        End Get
    End Property

    Public Function GetRegion() As Region()
        Dim DrawnRegions() As Region = Nothing
        DrawnRegions(0) = New Region
        Dim i As Integer = 0

        For Each oAnnContainer As AnnotationContainer In Me.oAnnotationContainers
            ReDim Preserve DrawnRegions(i + 1)
            DrawnRegions(0) = oAnnContainer.GetRegion
            i += 1
        Next
        Return DrawnRegions
    End Function

    Public Property AnnotationsURI() As Uri
        Set(ByVal Value As Uri)
            Me.oAnnotationsURI = Value
        End Set
        Get
            Return Me.oAnnotationsURI
        End Get
    End Property

    Public Property Updating() As Boolean
        'During update = true, no redraw and changed events
        Get
            Return Me.bUpdating
        End Get
        Set(ByVal Value As Boolean)
            If Value Then
                Debug.WriteLine("Starting update of annotation, no drawing!")
                Me.bUpdating = True
                Cursor = System.Windows.Forms.Cursors.WaitCursor
            Else
                Debug.WriteLine("Ending update of annotation, drawing on again")
                Cursor = System.Windows.Forms.Cursors.Arrow
                Me.bUpdating = False
                Me.RaiseChangedEvent(Nothing)
            End If
        End Set
    End Property

    Public Property AnnotationsManagerVisible() As Boolean
        Get
            Return oAnnotationsManager.Visible
        End Get
        Set(ByVal Value As Boolean)
            oAnnotationsManager.Visible = Value
        End Set
    End Property

    Public Property ImageURI() As Uri
        Set(ByVal Value As Uri)
            Me.oImageURI = Value
        End Set
        Get
            Return Me.oImageURI
        End Get
    End Property

    Public ReadOnly Property Masks() As MaskCollection
        Get
            Return Me.oMasks
        End Get
    End Property

    Public Property MaximimumSize() As System.Drawing.Size
        Get
            Return Me.objMaximimumSize
        End Get
        Set(ByVal Value As System.Drawing.Size)
            Me.objMaximimumSize = Value
        End Set
    End Property

    Public Property DisplayOffset() As Point
        Get
            Return Me.ptDisplayOffset
        End Get
        Set(ByVal Value As Point)
            Debug.WriteLine("Set display offset at " & Value.ToString(), "AnnotatedImage.DisplayOffset")
            If Value.X < 0 Then Value.X = 0
            If Value.Y < 0 Then Value.Y = 0
            Dim iDeltaX As Integer = CInt((DisplaySize.Width - Me.Width) / Me.DisplayZoom)
            Dim iDeltaY As Integer = CInt((DisplaySize.Height - Me.Height) / Me.DisplayZoom)

            If Value.X > iDeltaX Then Value.X = iDeltaX
            If Value.Y > iDeltaY Then Value.Y = iDeltaY
            Me.ptDisplayOffset = Value

            'This is exceptionally necessary.
            Me.Draw()
        End Set
    End Property

    Public Property DisplayZoom() As Single
        Get
            Return Me.sDisplayZoom
        End Get
        Set(ByVal Value As Single)
            If Math.Abs(Value - 1.0) < 0.001 Then Value = 1.0
            Dim objOffsetScale As Single = Value / Me.sDisplayZoom
            Me.sDisplayZoom = Value
            Debug.WriteLine("Set display zoom at " & Value, "AnnotatedImage.DisplayZoom")

            'We have to check if we have to reset or scale the display offset
            If DisplayImageBiggerThanCanvas() = False Then
                Me.ptDisplayOffset.X = 0
                Me.ptDisplayOffset.Y = 0
            Else
                Me.ptDisplayOffset.X = CInt(Me.ptDisplayOffset.X * objOffsetScale)
                Me.ptDisplayOffset.Y = CInt(Me.ptDisplayOffset.Y * objOffsetScale)
            End If

            RaiseEvent DisplayZoomChanged(Value)

            'Size the control if necessary
            SizeControl()
        End Set
    End Property

    Public Property PhysicalZoom() As Single
        'This zoom is real, i.e. distance on screen versus real distance ....
        Get
            Return Me.sDisplayZoom * Me.oBitmap.HorizontalResolution / SCREEN_DPI

        End Get
        Set(ByVal Value As Single)
            Me.DisplayZoom = Value * SCREEN_DPI / Me.oBitmap.HorizontalResolution
        End Set
    End Property

    Public ReadOnly Property DisplaySize() As System.Drawing.Size
        Get
            If Me.BitmapPresent Then
                Dim sZoom As Single = Me.DisplayZoom
                Return New System.Drawing.Size(CInt(Me.oBitmap.Width * sZoom), CInt(Me.oBitmap.Height * sZoom))
            Else
                Return New System.Drawing.Size(0, 0)
            End If
        End Get
    End Property

    'Some convenience properties for the bitmap
    Public ReadOnly Property ImageSize() As System.Drawing.Size
        Get
            Return New System.Drawing.Size(Me.oBitmap.Width, Me.oBitmap.Height)
        End Get
    End Property

    Public Property Resolution() As Single
        'This zoom is real, i.e. distance on screen versus real distance ....
        Get
            Return Me.oBitmap.HorizontalResolution
        End Get
        Set(ByVal Value As Single)
            Me.oBitmap.SetResolution(Value, Value)
        End Set
    End Property

    Public ReadOnly Property ColorDepth() As Integer
        Get
            Return Image.GetPixelFormatSize(Me.oBitmap.PixelFormat)
        End Get
    End Property

    Public ReadOnly Property BitmapPresent() As Boolean
        Get
            If Me.oBitmap Is Nothing Then
                Return False
            Else
                Return True
            End If
        End Get
    End Property

    Public Property Bitmap() As Bitmap
        'We chose not to clone the bitmap when getting it, as this avoids problems with annotations
        'being cleared when setting a modifed bitmap back (and saves memory). This means u can change the pixels and do not need
        'to Set it to see the results, while annotations remain in place!
        Get
            'We cannot know if this means a change in the bitmap data, so
            'its up to the calling application to set the Modified property if necessary!
            Return Me.oBitmap
        End Get

        Set(ByVal Value As Bitmap)
            Me.oAnnotationContainers.Clear()
            Me.oMasks.Clear()
            Me.oBitmap = Value
            Me.sDisplayZoom = ComputeInitialDisplayZoom()
            Me.ptDisplayOffset.X = 0
            Me.ptDisplayOffset.Y = 0
            SizeControl()
            RaiseEvent BitmapChanged(Value)
        End Set
    End Property

    Public Property PixelAveragingWindowSize() As Integer
        Get
            Return Me.iWindowSize
        End Get
        Set(ByVal Value As Integer)
            Me.iWindowSize = Value
            RaiseEvent PixelAveragingWindowSizeChanged(Value)
        End Set
    End Property

    Private Property Dirty() As Boolean
        'This indicates if XOR drawing has occured since last refresh, which is necessary for 
        'the routines drawing XOR (erasable) rectangles and lines...
        Get
            Return Me.bDirty
        End Get
        Set(ByVal Value As Boolean)
            Me.bDirty = Value
        End Set
    End Property

#End Region

#Region "Methods"

#Region "Coordinates"
    Public Function GetDisplayTransformMatrix() As Matrix
        'Return a matrix containing display zoom and offset, transforming image coordinates to client-screen coordinates
        Dim m As New Matrix
        m.Scale(Me.sDisplayZoom, Me.sDisplayZoom)
        m.Translate(-Me.sDisplayZoom * Me.ptDisplayOffset.X, -Me.sDisplayZoom * Me.ptDisplayOffset.Y)
        Return m
    End Function

#End Region

#Region "Annotation manager"
    Public Sub ShowAnnotationsManager(ByVal bShow As Boolean)
        Me.oAnnotationsManager.Visible = bShow
        Me.oAnnotationsManager.Owner = Me.FindForm()
    End Sub

    Public Sub ExtendAnnotationManagerMenu(ByVal strCaption As String, _
                                           ByVal iMenu As AnnotationManagerMenus, _
                                           ByVal f As System.EventHandler)
        'Allow extension of menus in annotationsmanager. This works fine for
        'simple tools, but not for very interactive or complex tools (no events are returned to allow control of the 
        'process)
        Select Case iMenu
            Case AnnotationManagerMenus.Annotation
                Me.oAnnotationsManager.ctxAnnotation.MenuItems.Add(strCaption, f)
            Case AnnotationManagerMenus.AnnotationContainer
                Me.oAnnotationsManager.ctxAnnotationContainer.MenuItems.Add(strCaption, f)
                'Case AnnotationManagerMenus.AnnotationNew
                '  Me.oAnnotationsManager.ctxAnnotation.MenuItems(0).MenuItems.Add(strCaption, f)
        End Select
    End Sub

    Public Function GetAnnotationManagerSelectedObject() As Object
        'Return the object associated with the current selected treeview node
        Return Me.oAnnotationsManager.AnnotationsTree.SelectedNode.Tag
    End Function
#End Region

#Region "Draw"
    ''' -----------------------------------------------------------------------------
    ''' <summary>
    ''' Draw the annotations. In order too avoid multiple unnecessary redraws this will not occur 
    ''' if the updating property is set true, except if bForce is true
    ''' </summary>
    ''' <param name="bForce">Force redraw regardless of the updating parameter. Use this carefully for OS commanded redraw.</param>
    ''' <remarks>This function will trigger the overridden OnPaint method
    ''' </remarks>
    ''' <history>
    ''' 	[yvdh]	30-Nov-04	Created
    ''' </history>
    ''' -----------------------------------------------------------------------------
    Public Overloads Sub Draw(ByVal bForce As Boolean)
        If Me.bUpdating = False Or bForce = True Then
            Me.Dirty = True
            Me.Invalidate()
            Debug.WriteLine("Requesting draw", "AnnotatedImage.Draw")
        Else
            'Debug.WriteLine("Draw request canceled during update", "AnnotatedImage.Draw")
        End If
    End Sub

    ''' -----------------------------------------------------------------------------
    ''' <summary>
    ''' Draw the annotations. In order too avoid multiple unnecessary redraws this will not occur 
    ''' if the updating property is set true.
    ''' </summary>
    ''' <remarks>This function will trigger the overridden OnPaint method
    ''' </remarks>
    ''' <history>
    ''' 	[yvdh]	30-Nov-04	Created
    ''' </history>
    ''' -----------------------------------------------------------------------------
    Public Overloads Sub Draw()
        Me.Draw(False)
    End Sub

    Public Sub DrawErasableRectangle(ByVal objRect As Rectangle)
        'Draw an eraseble rectangle.
        'Input is in client coordinates, static old rectangle is in screen coordinates!
        'Checks that we do not draw outside client area
        Static objOldRect As Rectangle

        'Take a middle of the road Color
        Dim objColor As New Color
        objColor = Color.Gray

        'Only erase if required, e.g. not when control has been refreshed ...
        If Me.Dirty = True And objOldRect.Width > 0 Then
            ControlPaint.DrawReversibleFrame(objOldRect, objColor, FrameStyle.Dashed)
        End If

        'Make sure we are not outside the client area
        objRect.Intersect(New Rectangle(0, 0, Me.Width, Me.Height))

        objOldRect = Me.RectangleToScreen(objRect)
        ControlPaint.DrawReversibleFrame(objOldRect, objColor, FrameStyle.Dashed)
        Me.Dirty = True
    End Sub

    Public Sub DrawErasableLine(ByVal objPt1 As Point, ByVal objPt2 As Point)
        'Draw an eraseble line.
        'Pts are in client coordinates
        'static old Pts are in screen coordinates!
        Static objOldPt1 As Point
        Static objOldPt2 As Point

        'Take a middle of the road ColorUtil
        Dim objColor As New Color
        objColor = Color.Gray

        'Avoid trying to erase a rectangle if canvas has just been redrawn!
        If Me.Dirty Then
            ControlPaint.DrawReversibleLine(objOldPt1, objOldPt2, objColor)
        End If

        'Convert to screen coordinates
        objOldPt1 = Me.PointToScreen(objPt1)
        objOldPt2 = Me.PointToScreen(objPt2)
        ControlPaint.DrawReversibleLine(objOldPt1, objOldPt2, objColor)

        Me.Dirty = True
    End Sub

    Protected Overrides Sub OnPaint(ByVal pe As System.Windows.Forms.PaintEventArgs)
        'Add your custom paint code here
        Dim crs As Cursor = Me.Cursor
        Me.Cursor = System.Windows.Forms.Cursors.WaitCursor

        'Draw the bitmap ...
        If Me.BitmapPresent Then
            'Paint the bitmap onto the control, taking the cliprectangle into account
            Dim sZoom As Single = Me.sDisplayZoom
            Dim srcRect As Rectangle = New Rectangle(CInt(Me.ptDisplayOffset.X + pe.ClipRectangle.X / sZoom), _
                                                     CInt(Me.ptDisplayOffset.Y + pe.ClipRectangle.Y / sZoom), _
                                                     CInt(Math.Ceiling(pe.ClipRectangle.Width / sZoom)), _
                                                     CInt(Math.Ceiling(pe.ClipRectangle.Height / sZoom)))
            Debug.WriteLine("Redrawing bitmap from rectangle " & srcRect.ToString & " onto " & pe.ClipRectangle.ToString & _
              " (Zoom " & sZoom & ", offset " & Me.ptDisplayOffset.ToString & ")", "AnnotatedImage.Paint")

            pe.Graphics.DrawImage(Me.oBitmap, pe.ClipRectangle, srcRect, GraphicsUnit.Pixel)

            'Draw masks in overlay if present. Note that these masks can be undersampled!!!
            Dim oMask As Bitmap
            For Each oMask In Me.oMasks
                Dim sUnderSampleFactor As Single = CSng(oMask.Width / Me.oBitmap.Width)
                Dim oMaskSrcRect As New Rectangle(CInt(srcRect.X * sUnderSampleFactor), _
                                                  CInt(srcRect.Y * sUnderSampleFactor), _
                                                  CInt(srcRect.Width * sUnderSampleFactor), _
                                                  CInt(srcRect.Height * sUnderSampleFactor))

                pe.Graphics.DrawImage(oMask, pe.ClipRectangle, oMaskSrcRect, GraphicsUnit.Pixel)
            Next
        Else
            Debug.WriteLine("No bitmap to redraw!", "AnnotatedImage.Paint")
        End If

        'Draw annotatations

        Debug.WriteLine("Redrawing AnnotationContainers ....", "Annotations.Draw")
        Dim g As Graphics = pe.Graphics
        Dim m As Drawing2D.Matrix = Me.GetDisplayTransformMatrix()

        'Quality settings
        'g.SmoothingMode = SmoothingMode.AntiAlias
        g.SmoothingMode = SmoothingMode.HighSpeed

        Dim objCon As AnnotationContainer
        For Each objCon In Me.oAnnotationContainers
            objCon.Draw(g, m)
        Next
        Debug.WriteLine("Done redrawing " & Me.oAnnotationContainers.Count & " AnnotationContainers", "Annotations.Draw")

        'Canvas is no longer dirty after full redraw
        Me.Dirty = False
        Me.Cursor = crs
    End Sub

#End Region

#Region "File IO"
    Public Function SaveImage(ByVal oUri As Uri) As Boolean
        Debug.WriteLine("Saving/Streaming image to " & oUri.ToString, "AnnotatedImage.SaveImage")

        If oUri.IsFile Then
            'Bitmap.save method sucks, it uses some internal flag of the bitmap to determine the 
            'filetype to use based on the load type, and disregards the file extension!!!!!!
            Dim strFilename As String = oUri.LocalPath
            Dim strExtension As String = System.IO.Path.GetExtension(strFilename)
            Me.Bitmap.Save(strFilename, SystemUtil.GetImageFileType(strFilename))
            Me.oImageURI = oUri
        Else
            'We are assuming that the URI can be used to stream image back to web server
            'Create a webrequest and stream from resource
            Dim oWebclient As New System.Net.WebClient
            Try
                'Normally method is PUT, but when using encryption we use a special httphandler requiring POST
                Dim strMethod As String = "PUT"
                If oUri.Query.Length > 0 Then strMethod = "POST"

                Dim oStream As IO.Stream = oWebclient.OpenWrite(oUri.ToString, strMethod)
                Dim oImageStream As New IO.MemoryStream
                Me.Bitmap.Save(oImageStream, Me.Bitmap.RawFormat)
                oStream.Write(oImageStream.ToArray(), 0, CInt(oImageStream.Length))
                oStream.Close()
                Me.oImageURI = oUri
            Catch ex As Exception
                Debug.WriteLine("Error" & ex.Message, "AnnotatedImage.SaveImage")
                MessageBox.Show("The follwoing error occured while streaming the image back to the webserver: " & ex.Message, _
                  "Error streaming image to webserver", MessageBoxButton.OK, MessageBoxImage.Error, MessageBoxResult.OK)
                Return False
            End Try
        End If

        Return True
    End Function

    Public Overloads Function LoadImage(ByVal oURI As Uri) As Boolean
        'Allow URI download
        Debug.WriteLine("Loading image from " & oURI.ToString, "AnnotatedImage.LoadImage")
        Dim objMemoryBitmap As Bitmap

        Try
            If oURI.IsFile Then
                Dim objBitmap As Bitmap = CType(Image.FromFile(oURI.LocalPath), Drawing.Bitmap), bSucces As Boolean = False
                'Create an in-memory copy, and assign to custom control
                objMemoryBitmap = New Bitmap(objBitmap.Width, objBitmap.Height, objBitmap.PixelFormat)
                objMemoryBitmap.SetResolution(objBitmap.HorizontalResolution, objBitmap.VerticalResolution)
                Dim g As Graphics = Graphics.FromImage(objMemoryBitmap)
                g.DrawImage(objBitmap, New Rectangle(0, 0, objBitmap.Width, objBitmap.Height))
                g.Dispose()
                objBitmap.Dispose()

            Else
                'Create a webrequest and stream from resource
                Dim oWebclient As New System.Net.WebClient
                objMemoryBitmap = New Bitmap(oWebclient.OpenRead(oURI.ToString))
            End If
        Catch ex As Exception
            Debug.WriteLine("Failed to load image from URI " & oURI.ToString & " (" & ex.Message & ")", "AnnotatedImage.LoadImage")
            Me.oImageURI = Nothing
            Return False
        End Try

        'Set bitmap
        Me.oImageURI = oURI
        Me.Bitmap = objMemoryBitmap
        Return True
    End Function

    Public Sub LoadAnnotations(ByVal oUri As Uri, Optional ByVal getPassKey As Boolean = False)
        'Avoid extra redraw operations!
        Debug.WriteLine("Loading annotations from " & oUri.ToString, "Annotations.Load")

        Me.Updating = True
        Dim SVGDoc As New Xml.XmlDocument

        If oUri.LocalPath.Contains(".csv") Then
            Dim Temp As New OpenFileDialog
            Temp.Title = "Open a Sample SVG file"
            Temp.Filter = "Scalable Vector graphics (*.svg)|*.svg"
            Temp.AddExtension = True
            If Temp.ShowDialog() = DialogResult.OK Then
                If Temp.FileName.Length > 0 Then
                    Me.LoadAnnotations(New Uri(Temp.FileName))

                End If
            End If

            'Load CSV File
            Me.AnnotationContainers.Clear()

        Else

            If oUri.IsFile Then
                'It is very important to set the resolvers to nothing, else the 
                'XML files will be checked vs their schemas, thereby connecting to the internet
                'and needing proxy authentication!

                'COMMENT. Try .. catch is quite slow, and as the annotations file will often be missing, we
                'test for this first
                'Clear existing annotations.
                Me.AnnotationContainers.Clear()
                If System.IO.File.Exists(oUri.LocalPath) Then
                    Try
                        Dim txtReader As New System.IO.StreamReader(oUri.LocalPath)
                        Dim txtXMLReader As New Xml.XmlTextReader(txtReader)
                        txtXMLReader.XmlResolver = Nothing
                        SVGDoc.XmlResolver = Nothing
                        SVGDoc.Load(txtXMLReader)
                        txtReader.Close()
                        Me.oAnnotationsURI = oUri
                    Catch ex As Exception
                        Debug.WriteLine("Failed to read file URI " & oUri.ToString & " (" & ex.Message & ")", "AnnotatedImage.LoadAnnotations")
                        Me.Updating = False
                        Exit Sub
                    End Try
                Else
                    Debug.WriteLine("File URI " & oUri.ToString & " does not exist", "AnnotatedImage.LoadAnnotations")
                End If
            Else
                'We set the URI, even if a load can still fail. This allows passing an non-existing filename for 
                'save afterwards!
                Me.oAnnotationsURI = oUri

                Try
                    Dim oWebclient As New System.Net.WebClient
                    Dim oStream As System.IO.Stream = oWebclient.OpenRead(oUri.ToString)
                    SVGDoc.XmlResolver = Nothing
                    SVGDoc.Load(oStream)
                    oStream.Close()
                Catch ex As Exception
                    Debug.WriteLine("Failed to read URI stream " & oUri.ToString & " (" & ex.Message & ")", "AnnotatedImage.LoadAnnotations")
                    Me.Updating = False
                    Exit Sub
                End Try
            End If

            'Now look for g, they represent containers
            Dim oCNodes As Xml.XmlNodeList = SVGDoc.SelectNodes("/svg/g")
            Dim oCNode As Xml.XmlNode
            For Each oCNode In oCNodes
                'Create containers, with ID as key
                Dim strCKey As String = oCNode.Attributes.GetNamedItem("id").Value
                Dim oCColor As Color = WebColorToColor(oCNode.Attributes.GetNamedItem("fill").Value)
                Dim iCOpacity As Integer = CInt(Math.Round(Single.Parse(oCNode.Attributes.GetNamedItem("fill-opacity").Value) * 255))
                oCColor = Color.FromArgb(iCOpacity, oCColor)

                Dim oAnnContainer As New AnnotationContainer(oCColor)
                Me.AnnotationContainers.Add(oAnnContainer, strCKey)

                'Title = text. Text visible if text node present
                oAnnContainer.Text = GetSVGText(oCNode, "title")

                'Now look for g again, they represent annotations and contain a polyline.
                Dim oAGNodes As Xml.XmlNodeList = oCNode.SelectNodes("g")
                Dim oAGNode As Xml.XmlNode
                For Each oAGNode In oAGNodes
                    Dim oAnn As New Annotation

                    'Title = text. Text visible if text node present
                    oAnn.Text = GetSVGText(oAGNode, "title")

                    'Points visible if Marker is present
                    Dim oMarkerNode As Xml.XmlNode = oAGNode.SelectSingleNode("./defs/Marker")
                    If Not oMarkerNode Is Nothing Then
                        oAnn.PointSize = Integer.Parse(oMarkerNode.Attributes.GetNamedItem("marker-height").Value)
                    Else
                        oAnn.PointSize = 0
                    End If

                    Dim oANode As Xml.XmlNode = oAGNode.SelectSingleNode("polyline")
                    If Not oANode Is Nothing Then
                        Dim strAKey As String = oANode.Attributes.GetNamedItem("id").Value
                        Dim strPassKey As String = oANode.Attributes.GetNamedItem("PassKey").Value
                        Dim oAColor As Color = WebColorToColor(oANode.Attributes.GetNamedItem("stroke").Value)

                        oAnn.Color = oAColor
                        oAnnContainer.Annotations.Add(oAnn, strAKey)

                        Dim oPtNode As Xml.XmlNode = oANode.Attributes.GetNamedItem("points")
                        If Not oPtNode Is Nothing And (strPassKey = Me.PassKey Or Me.PassKey = "" Or getPassKey = True) Then
                            Dim strPts As String = oPtNode.Value
                            Me.PassKey = strPassKey
                            'We need to parse this string to x y coordinates
                            strPts = strPts.Trim()
                            Dim strPt() As String = strPts.Split(CChar(","))
                            If strPt.Length Mod 2 <> 0 Then
                                'Should be even!!!!
                                ReDim Preserve strPt(strPt.Length - 2)
                            End If

                            Dim i As Integer
                            For i = 0 To strPt.Length - 1 Step 2
                                Dim oPt As New Point(CInt(strPt(i)), CInt(strPt(i + 1)))
                                oAnn.Points.Add(oPt)
                            Next

                            'If first point is same as last it is a closed contour!!!
                            Dim iNrPts As Integer = oAnn.Points.Count
                            If oAnn.Points.Count > 1 And oAnn.Points(0).X = oAnn.Points(iNrPts - 1).X And oAnn.Points(0).Y = oAnn.Points(iNrPts - 1).Y Then
                                oAnn.Closed = True

                                'Remove last point
                                oAnn.Points.RemoveAt(iNrPts - 1)
                            End If
                        End If
                    End If
                Next
            Next

            Me.Updating = False
        End If

    End Sub
    Public Function LoadCSV(ByVal FileName As String) As Integer(,,)
        Dim t As New IO.FileInfo(FileName)
        Dim tex As IO.StreamReader = t.OpenText
        Dim Line() As String
        Dim Points(), FinalPoints() As MatrixDataStructures.Coordinate
        Dim i As Integer = 0
        ReDim Points(i), FinalPoints(i)
        While Not tex.EndOfStream
            Line = tex.ReadLine().Split(CChar(","))
            ReDim Preserve Points(i + 1)
            Points(i) = New MatrixDataStructures.Coordinate(CInt(Line(0)), CInt(Line(1)), CInt(Line(2)))
            i += 1
        End While
        If Points.Length > 4 Then
            If Points(0).x = Points(1).x And Points(1).x = Points(i).x Then
                If Points(0).y = Points(1).y - 1 And Points(0).y = Points(2).y - 2 And Points(0).y = Points(3).y - 3 Then
                    'y is the order field x is the null field
                    FinalPoints(FinalPoints.Length) = Points(0)
                    For j As Integer = 1 To i - 1
                        If FinalPoints(FinalPoints.Length).y <> Points(j).y Then
                            FinalPoints(FinalPoints.Length) = Points(j - 1)
                            ReDim Preserve FinalPoints(FinalPoints.Length + 1)
                            FinalPoints(FinalPoints.Length) = Points(j)
                            ReDim Preserve FinalPoints(FinalPoints.Length + 1)
                        End If
                    Next
                Else
                    'z is the order field and x is the null field
                    FinalPoints(FinalPoints.Length) = Points(0)
                    For j As Integer = 1 To i - 1
                        If FinalPoints(FinalPoints.Length).x <> Points(j).x Then
                            FinalPoints(FinalPoints.Length) = Points(j - 1)
                            ReDim Preserve FinalPoints(FinalPoints.Length + 1)
                            FinalPoints(FinalPoints.Length) = Points(j)
                            ReDim Preserve FinalPoints(FinalPoints.Length + 1)
                        End If
                    Next
                End If
            ElseIf Points(0).y = Points(1).y And Points(1).y = Points(i).y Then

            Else
            End If
        End If
        Return Nothing
    End Function
    Public Function SaveAnnotations(ByVal oUri As Uri) As Boolean
        'Save annotations as SVG in URI. This allows annotations to be viewed 
        'in a browser , e.g. with the adobe plugin.
        Debug.WriteLine("Saving/Streaming annotations to " & oUri.ToString, "Annotations.Save")

        If Me.oAnnotationContainers.Count > 0 Then
            Try
                Dim SVGXMLFile As Xml.XmlTextWriter

                If oUri.IsFile Then
                    SVGXMLFile = New Xml.XmlTextWriter(oUri.LocalPath, System.Text.Encoding.UTF8)
                Else
                    Dim oWebclient As New System.Net.WebClient

                    'Normally method is PUT, but when using encryption we use a special httphandler requiring POST
                    Dim strMethod As String = "PUT"
                    If oUri.Query.Length > 0 Then strMethod = "POST"

                    Dim oStream As IO.Stream = oWebclient.OpenWrite(oUri.ToString, strMethod)
                    SVGXMLFile = New Xml.XmlTextWriter(oStream, System.Text.Encoding.UTF8)
                End If

                SVGXMLFile.WriteStartDocument(False)
                SVGXMLFile.WriteDocType("svg", Nothing, "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd", Nothing)
                SVGXMLFile.WriteStartElement("svg")
                SVGXMLFile.WriteAttributeString("width", CStr(Me.ImageSize.Width))
                SVGXMLFile.WriteAttributeString("height", CStr(Me.ImageSize.Height))

                'Each annotationcontainer will be in its SVG group (g element)
                Dim oContainer As AnnotationContainer
                For Each oContainer In Me.AnnotationContainers
                    SVGXMLFile.WriteStartElement("g")

                    SVGXMLFile.WriteAttributeString("id", oContainer.Key)
                    SVGXMLFile.WriteAttributeString("fill", ColorToWebColor(oContainer.Color))
                    SVGXMLFile.WriteAttributeString("fill-rule", "evenodd")
                    SVGXMLFile.WriteAttributeString("fill-opacity", CStr(oContainer.Color.A / 255.0))

                    'Add text in title. Render as text if possible
                    If Not oContainer.Text Is Nothing Then
                        SVGXMLFile.WriteElementString("title", oContainer.Text)
                        If oContainer.Annotations.Count > 0 Then
                            If oContainer.Annotations(0).Points.Count > 0 Then
                                AddSVGText(SVGXMLFile, oContainer.Text, oContainer.Annotations(0).Points(0), 12, oContainer.Color)
                            End If
                        End If
                    End If

                    'Write an annotation as polyline. We put it also in a group so that a title and other 
                    'metadata can be added ...
                    Dim oAnn As Annotation
                    For Each oAnn In oContainer.Annotations
                        SVGXMLFile.WriteStartElement("g")

                        'Marker
                        Dim strMarkerID As String = "M" & oAnn.Key

                        If oAnn.PointSize >= 0 Then
                            SVGXMLFile.WriteStartElement("defs")
                            SVGXMLFile.WriteStartElement("Marker")
                            SVGXMLFile.WriteAttributeString("id", strMarkerID)
                            SVGXMLFile.WriteAttributeString("markerUnits", "strokeWidth")
                            SVGXMLFile.WriteAttributeString("orient", "auto")
                            SVGXMLFile.WriteAttributeString("marker-height", CStr(oAnn.PointSize))
                            SVGXMLFile.WriteAttributeString("marker-width", CStr(oAnn.PointSize))
                            SVGXMLFile.WriteStartElement("path")
                            SVGXMLFile.WriteAttributeString("d", "M 0 0 L 0 " & oAnn.PointSize & " " & oAnn.PointSize & " " & oAnn.PointSize & " " & oAnn.PointSize & " 0 z")
                            SVGXMLFile.WriteEndElement()
                            SVGXMLFile.WriteEndElement()
                            SVGXMLFile.WriteEndElement()
                        End If

                        'Add text in title. Render as text if possible
                        If Not oAnn.Text Is Nothing Then
                            SVGXMLFile.WriteElementString("title", oAnn.Text)
                            If oAnn.Points.Count > 0 Then
                                'Add text if present, at location of 1st point
                                AddSVGText(SVGXMLFile, oAnn.Text, oAnn.Points(0), 12, oAnn.Color)
                            End If
                        End If

                        SVGXMLFile.WriteStartElement("polyline")
                        SVGXMLFile.WriteAttributeString("id", oAnn.Key)
                        SVGXMLFile.WriteAttributeString("PassKey", Me.PassKey)

                        'Add stroke
                        SVGXMLFile.WriteAttributeString("stroke", ColorToWebColor(oAnn.Color))
                        SVGXMLFile.WriteAttributeString("stroke-width", CStr(oAnn.LineWidth))

                        'Add markers
                        If oAnn.PointSize > 0 Then SVGXMLFile.WriteAttributeString("marker", "url(#" & strMarkerID & ")")

                        'Add the points
                        Dim oPt As Point, strPt As String
                        strPt = ""
                        For Each oPt In oAnn.Points
                            strPt = strPt & oPt.X & "," & oPt.Y & ","
                        Next

                        If oAnn.Closed And oAnn.Points.Count > 0 Then
                            'Add first point again
                            strPt = strPt & oAnn.Points(0).X & "," & oAnn.Points(0).Y & ","
                        End If

                        'Remove last comma
                        If strPt.Length > 0 Then
                            strPt = strPt.Substring(0, strPt.Length - 1)
                            SVGXMLFile.WriteAttributeString("points", strPt)
                        End If
                        SVGXMLFile.WriteEndElement()

                        'Close group
                        SVGXMLFile.WriteEndElement()
                    Next
                    SVGXMLFile.WriteEndElement()

                Next
                SVGXMLFile.WriteEndElement()
                SVGXMLFile.WriteEndDocument()
                SVGXMLFile.Close()

                Return True
            Catch ex As Exception
                Debug.WriteLine("Error save SVG file " & oUri.ToString & "(" & ex.Message & ")")
                Return False
            End Try
        Else
            'Just erase the file if it exists
            If oUri.IsFile Then
                Dim strFile As String = oUri.LocalPath
                If System.IO.File.Exists(strFile) Then
                    System.IO.File.Delete(strFile)
                End If
            Else
                'We should create an empty stream and make sure the httphandler erase the file ...
                Dim oWebclient As New System.Net.WebClient

                'Normally method is PUT, but when using encryption we use a special httphandler requiring POST
                Dim strMethod As String = "PUT"
                If oUri.Query.Length > 0 Then strMethod = "POST"

                Dim oStream As IO.Stream = oWebclient.OpenWrite(oUri.ToString, strMethod)
                oStream.Close()

            End If
        End If
    End Function

    Public Function AppendAnnotations(ByVal oUri As Uri) As Boolean
        'Save annotations as SVG in URI. This allows annotations to be viewed 
        'in a browser , e.g. with the adobe plugin.
        Dim SVGDoc As New Xml.XmlDocument

        If Me.oAnnotationContainers.Count > 0 Then
            Dim txtReader As New System.IO.StreamReader(oUri.LocalPath)
            Dim txtXMLReader As New Xml.XmlTextReader(txtReader)
            txtXMLReader.XmlResolver = Nothing
            SVGDoc.XmlResolver = Nothing
            SVGDoc.Load(txtXMLReader)
            txtReader.Close()
            Me.oAnnotationsURI = oUri

        End If

        'Now look for g, they represent containers
        Dim oCNodes As Xml.XmlNodeList = SVGDoc.SelectNodes("/svg/g")
        Dim oCNode As Xml.XmlNode

        Dim TempAnnotationContainer As New AnnotationContainerCollection(Me)
        For Each oCNode In oCNodes
            'Create containers, with ID as key
            Dim strCKey As String = oCNode.Attributes.GetNamedItem("id").Value
            Dim oCColor As Color = WebColorToColor(oCNode.Attributes.GetNamedItem("fill").Value)
            Dim iCOpacity As Integer = CInt(Math.Round(Single.Parse(oCNode.Attributes.GetNamedItem("fill-opacity").Value) * 255))
            oCColor = Color.FromArgb(iCOpacity, oCColor)

            Dim oAnnContainer As New AnnotationContainer(oCColor)
            TempAnnotationContainer.Add(oAnnContainer, strCKey)

            'Now look for g again, they represent annotations and contain a polyline.
            Dim oAGNodes As Xml.XmlNodeList = oCNode.SelectNodes("g")
            Dim oAGNode As Xml.XmlNode
            Dim oAnn As Annotation
            For Each oAGNode In oAGNodes
                oAnn = New Annotation

                'Title = text. Text visible if text node present
                oAnn.Text = GetSVGText(oAGNode, "title")

                'Points visible if Marker is present
                Dim oMarkerNode As Xml.XmlNode = oAGNode.SelectSingleNode("./defs/Marker")
                If Not oMarkerNode Is Nothing Then
                    oAnn.PointSize = Integer.Parse(oMarkerNode.Attributes.GetNamedItem("marker-height").Value)
                Else
                    oAnn.PointSize = 0
                End If

                Dim oANode As Xml.XmlNode = oAGNode.SelectSingleNode("polyline")
                If Not oANode Is Nothing Then
                    Dim strAKey As String = oANode.Attributes.GetNamedItem("id").Value
                    Dim strPassKey As String = oANode.Attributes.GetNamedItem("PassKey").Value
                    Dim oAColor As Color = WebColorToColor(oANode.Attributes.GetNamedItem("stroke").Value)

                    oAnn.Color = oAColor
                    oAnn.PassKey = strPassKey

                    oAnnContainer.Annotations.Add(oAnn, strAKey)

                    Dim oPtNode As Xml.XmlNode = oANode.Attributes.GetNamedItem("points")
                    If Not oPtNode Is Nothing Then
                        Dim strPts As String = oPtNode.Value
                        'We need to parse this string to x y coordinates
                        strPts = strPts.Trim()
                        Dim strPt() As String = strPts.Split(CChar(","))
                        If strPt.Length Mod 2 <> 0 Then
                            'Should be even!!!!
                            ReDim Preserve strPt(strPt.Length - 2)
                        End If

                        Dim i As Integer
                        For i = 0 To strPt.Length - 1 Step 2
                            Dim oPt As New Point(CInt(strPt(i)), CInt(strPt(i + 1)))
                            oAnn.Points.Add(oPt)
                        Next

                        'If first point is same as last it is a closed contour!!!
                        Dim iNrPts As Integer = oAnn.Points.Count
                        If oAnn.Points.Count > 1 And oAnn.Points(0).X = oAnn.Points(iNrPts - 1).X And oAnn.Points(0).Y = oAnn.Points(iNrPts - 1).Y Then
                            oAnn.Closed = True

                            'Remove last point
                            oAnn.Points.RemoveAt(iNrPts - 1)
                        End If

                    End If

                End If

            Next
        Next
        Dim strFile As String = oUri.LocalPath
        If System.IO.File.Exists(strFile) Then
            System.IO.File.Delete(strFile)
        End If


        'MsgBox("1-" & Me.oAnnotationContainers.Count)
        'MsgBox("2-" & TempAnnotationContainer.Count)

        Dim SVGXMLFile As Xml.XmlTextWriter

        SVGXMLFile = New Xml.XmlTextWriter(oUri.LocalPath, System.Text.Encoding.UTF8)

        SVGXMLFile.WriteStartDocument(False)
        SVGXMLFile.WriteDocType("svg", Nothing, "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd", Nothing)
        SVGXMLFile.WriteStartElement("svg")
        SVGXMLFile.WriteAttributeString("width", CStr(Me.ImageSize.Width))
        SVGXMLFile.WriteAttributeString("height", CStr(Me.ImageSize.Height))

        'Each annotationcontainer will be in its SVG group (g element)
        Dim oContainer As AnnotationContainer

        For Each oContainer In Me.AnnotationContainers
            SVGXMLFile.WriteStartElement("g")

            SVGXMLFile.WriteAttributeString("id", oContainer.Key)
            SVGXMLFile.WriteAttributeString("fill", ColorToWebColor(oContainer.Color))
            SVGXMLFile.WriteAttributeString("fill-rule", "evenodd")
            SVGXMLFile.WriteAttributeString("fill-opacity", CStr(oContainer.Color.A / 255.0))

            'Add text in title. Render as text if possible
            If Not oContainer.Text Is Nothing Then
                SVGXMLFile.WriteElementString("title", oContainer.Text)
                If oContainer.Annotations.Count > 0 Then
                    If oContainer.Annotations(0).Points.Count > 0 Then
                        AddSVGText(SVGXMLFile, oContainer.Text, oContainer.Annotations(0).Points(0), 12, oContainer.Color)
                    End If
                End If
            End If

            'Write an annotation as polyline. We put it also in a group so that a title and other 
            'metadata can be added ...
            Dim oAnn As Annotation
            For Each oAnn In oContainer.Annotations
                SVGXMLFile.WriteStartElement("g")

                'Marker
                Dim strMarkerID As String = "M" & oAnn.Key

                If oAnn.PointSize > 0 Then
                    SVGXMLFile.WriteStartElement("defs")
                    SVGXMLFile.WriteStartElement("Marker")
                    SVGXMLFile.WriteAttributeString("id", strMarkerID)
                    SVGXMLFile.WriteAttributeString("markerUnits", "strokeWidth")
                    SVGXMLFile.WriteAttributeString("orient", "auto")
                    SVGXMLFile.WriteAttributeString("marker-height", CStr(oAnn.PointSize))
                    SVGXMLFile.WriteAttributeString("marker-width", CStr(oAnn.PointSize))
                    SVGXMLFile.WriteStartElement("path")
                    SVGXMLFile.WriteAttributeString("d", "M 0 0 L 0 " & oAnn.PointSize & " " & oAnn.PointSize & " " & oAnn.PointSize & " " & oAnn.PointSize & " 0 z")
                    SVGXMLFile.WriteEndElement()
                    SVGXMLFile.WriteEndElement()
                    SVGXMLFile.WriteEndElement()
                End If

                'Add text in title. Render as text if possible
                If Not oAnn.Text Is Nothing Then
                    SVGXMLFile.WriteElementString("title", oAnn.Text)
                    If oAnn.Points.Count > 0 Then
                        'Add text if present, at location of 1st point
                        AddSVGText(SVGXMLFile, oAnn.Text, oAnn.Points(0), 12, oAnn.Color)
                    End If
                End If

                SVGXMLFile.WriteStartElement("polyline")
                SVGXMLFile.WriteAttributeString("id", oAnn.Key)
                'MsgBox(Me.PassKey)
                SVGXMLFile.WriteAttributeString("PassKey", Me.PassKey)

                'Add stroke
                SVGXMLFile.WriteAttributeString("stroke", ColorToWebColor(oAnn.Color))
                SVGXMLFile.WriteAttributeString("stroke-width", CStr(oAnn.LineWidth))

                'Add markers
                If oAnn.PointSize > 0 Then SVGXMLFile.WriteAttributeString("marker", "url(#" & strMarkerID & ")")

                'Add the points
                Dim oPt As Point, strPt As String
                strPt = ""
                For Each oPt In oAnn.Points
                    strPt = strPt & oPt.X & "," & oPt.Y & ","
                Next

                If oAnn.Closed And oAnn.Points.Count > 0 Then
                    'Add first point again
                    strPt = strPt & oAnn.Points(0).X & "," & oAnn.Points(0).Y & ","
                End If

                'Remove last comma
                If strPt.Length > 0 Then
                    strPt = strPt.Substring(0, strPt.Length - 1)
                    SVGXMLFile.WriteAttributeString("points", strPt)
                End If
                SVGXMLFile.WriteEndElement()

                'Close group
                SVGXMLFile.WriteEndElement()
            Next
            SVGXMLFile.WriteEndElement()

        Next
        For Each oContainer In TempAnnotationContainer
            SVGXMLFile.WriteStartElement("g")

            SVGXMLFile.WriteAttributeString("id", oContainer.Key)
            SVGXMLFile.WriteAttributeString("fill", ColorToWebColor(oContainer.Color))
            SVGXMLFile.WriteAttributeString("fill-rule", "evenodd")
            SVGXMLFile.WriteAttributeString("fill-opacity", CStr(oContainer.Color.A / 255.0))

            'Add text in title. Render as text if possible
            If Not oContainer.Text Is Nothing Then
                SVGXMLFile.WriteElementString("title", oContainer.Text)
                If oContainer.Annotations.Count > 0 Then
                    If oContainer.Annotations(0).Points.Count > 0 Then
                        AddSVGText(SVGXMLFile, oContainer.Text, oContainer.Annotations(0).Points(0), 12, oContainer.Color)
                    End If
                End If
            End If

            'Write an annotation as polyline. We put it also in a group so that a title and other 
            'metadata can be added ...
            Dim oAnn As Annotation
            For Each oAnn In oContainer.Annotations
                SVGXMLFile.WriteStartElement("g")

                'Marker
                Dim strMarkerID As String = "M" & oAnn.Key

                If oAnn.PointSize > 0 Then
                    SVGXMLFile.WriteStartElement("defs")
                    SVGXMLFile.WriteStartElement("Marker")
                    SVGXMLFile.WriteAttributeString("id", strMarkerID)
                    SVGXMLFile.WriteAttributeString("markerUnits", "strokeWidth")
                    SVGXMLFile.WriteAttributeString("orient", "auto")
                    SVGXMLFile.WriteAttributeString("marker-height", CStr(oAnn.PointSize))
                    SVGXMLFile.WriteAttributeString("marker-width", CStr(oAnn.PointSize))
                    SVGXMLFile.WriteStartElement("path")
                    SVGXMLFile.WriteAttributeString("d", "M 0 0 L 0 " & oAnn.PointSize & " " & oAnn.PointSize & " " & oAnn.PointSize & " " & oAnn.PointSize & " 0 z")
                    SVGXMLFile.WriteEndElement()
                    SVGXMLFile.WriteEndElement()
                    SVGXMLFile.WriteEndElement()
                End If

                'Add text in title. Render as text if possible
                If Not oAnn.Text Is Nothing Then
                    SVGXMLFile.WriteElementString("title", oAnn.Text)
                    If oAnn.Points.Count > 0 Then
                        'Add text if present, at location of 1st point
                        AddSVGText(SVGXMLFile, oAnn.Text, oAnn.Points(0), 12, oAnn.Color)
                    End If
                End If

                SVGXMLFile.WriteStartElement("polyline")
                SVGXMLFile.WriteAttributeString("id", oAnn.Key)
                SVGXMLFile.WriteAttributeString("PassKey", oAnn.PassKey)

                'Add stroke
                SVGXMLFile.WriteAttributeString("stroke", ColorToWebColor(oAnn.Color))
                SVGXMLFile.WriteAttributeString("stroke-width", CStr(oAnn.LineWidth))

                'Add markers
                If oAnn.PointSize > 0 Then SVGXMLFile.WriteAttributeString("marker", "url(#" & strMarkerID & ")")

                'Add the points
                Dim oPt As Point, strPt As String
                strPt = ""
                For Each oPt In oAnn.Points
                    strPt = strPt & oPt.X & "," & oPt.Y & ","
                Next

                If oAnn.Closed And oAnn.Points.Count > 0 Then
                    'Add first point again
                    strPt = strPt & oAnn.Points(0).X & "," & oAnn.Points(0).Y & ","
                End If

                'Remove last comma
                If strPt.Length > 0 Then
                    strPt = strPt.Substring(0, strPt.Length - 1)
                    SVGXMLFile.WriteAttributeString("points", strPt)
                End If
                SVGXMLFile.WriteEndElement()

                'Close group
                SVGXMLFile.WriteEndElement()
            Next
            SVGXMLFile.WriteEndElement()

        Next
        SVGXMLFile.WriteEndElement()
        SVGXMLFile.WriteEndDocument()
        SVGXMLFile.Close()

        Return True

    End Function

    Private Function GetSVGText(ByVal oParent As Xml.XmlNode, ByVal strName As String) As String
        'return the text IF node exists!
        Dim oNode As Xml.XmlNode = oParent.Item(strName)
        If oNode Is Nothing Then
            Return ""
        Else
            Return oNode.InnerText
        End If
    End Function

    Private Sub AddSVGText(ByVal SVGXMLFile As Xml.XmlTextWriter, ByVal strText As String, ByVal oPt As Point, ByVal iFontSize As Integer, ByVal oColor As Color)
        SVGXMLFile.WriteStartElement("text")
        SVGXMLFile.WriteAttributeString("x", CStr(oPt.X))
        SVGXMLFile.WriteAttributeString("y", CStr(oPt.Y))
        SVGXMLFile.WriteAttributeString("font-family", "Verdana")
        SVGXMLFile.WriteAttributeString("font-size", CStr(iFontSize))
        SVGXMLFile.WriteAttributeString("fill", ColorToWebColor(oColor))
        SVGXMLFile.WriteAttributeString("fill-opacity", "255")
        SVGXMLFile.WriteAttributeString("stroke", ColorToWebColor(oColor))
        SVGXMLFile.WriteString(strText)
        SVGXMLFile.WriteEndElement()
    End Sub

    Private Function ColorToWebColor(ByVal oColor As Color) As String
        Dim strColor As String = Hex(oColor.ToArgb And &HFFFFFF)
        If strColor.Length < 6 Then strColor = "00" & strColor
        strColor = "#" & strColor
        Return strColor
    End Function

    Private Function WebColorToColor(ByVal strColor As String) As Color
        strColor = "FF" & strColor.Substring(1)
        Dim iColor As Integer = Integer.Parse(strColor, System.Globalization.NumberStyles.HexNumber)
        Return Color.FromArgb(iColor)
    End Function

#End Region

#Region "Interactive creation"
    Public Sub CreateAnnotation(ByVal objAnn As Annotation)
        ' Annotation MUST have been added to an AnnotationContainer!!!!
        Debug.WriteLine("Creating annotation with key " & objAnn.Key & " interactively", "Annotations.CreateAnnotation")
        If Me.bCreatingAnnotation = True Then
            MessageBox.Show("You cannot create 2 annotations at the same time!", "Create Annotation", MessageBoxButton.OK, MessageBoxImage.Error)
        Else
            Dim objannAnnotationContainer As AnnotationContainer = objAnn.oAnnotationContainer
            If objannAnnotationContainer Is Nothing Then
                MessageBox.Show("Interactive annotation must be added to AnnotationContainer before calling DefineAnnotationInteractively", "Create Annotation", MessageBoxButton.OK, MessageBoxImage.Exclamation)
            Else
                'Private properties for creation
                Me.bCreatingAnnotation = True
                Me.strCreatedAnnotationKey = objAnn.Key
                Me.strCreatedAnnotationAnnotationContainerKey = objannAnnotationContainer.Key
            End If
        End If
    End Sub

    Private Function CanOrMustExitAnnotationCreation(ByVal objAnn As Annotation, ByVal bRequestEnd As Boolean) As Boolean
        'Check whether we can or must end the annotation creation.
        'Return true when we request and we must not enter a fixed enumber of points
        'Returns true when the required number of points is reached regardless of the request
        'Returns false else
        Dim bDoneCreating As Boolean = False
        Debug.WriteLine("Checking for exit of annotation creation", "Annotations.ExitAnnotationCreation")

        If objAnn.MaximumNumberOfPoints > -1 Then
            'Automatic exit is on when the number of pts is reached, and NOT before
            If bRequestEnd Then
                If objAnn.MaximumNumberOfPoints <> objAnn.Points.Count Then
                    MessageBox.Show("You still have to create " & objAnn.MaximumNumberOfPoints - objAnn.Points.Count & " point before exitting annotation creation!", _
                                    "Annotation creation", MessageBoxButton.OK, MessageBoxImage.Error)
                Else
                    bDoneCreating = True
                End If
            Else
                If objAnn.MaximumNumberOfPoints = objAnn.Points.Count Then bDoneCreating = True
            End If
        Else
            If bRequestEnd Then
                bDoneCreating = True
            End If
        End If

        Return bDoneCreating
    End Function

    Private Sub ExitAnnotationCreation(ByVal objAnn As Annotation)
        Debug.WriteLine("Exit of annotation creation", "Annotations.ExitAnnotationCreation")

        'Remove any duplicate points or points too close to each other!
        objAnn.Points.Quantize(CREATED_ANNOTATION_MIN_DISTANCE)
        Me.bCreatingAnnotation = False
        RaiseEvent AnnotationCreationDone(objAnn)

        Me.Draw()
    End Sub

    Private Sub CancelAnnotationCreation(ByVal objAnn As Annotation)
        'Escape annotation creation!
        objAnn.Points.Clear()
        Me.bCreatingAnnotation = False
        RaiseEvent AnnotationCreationCanceled(objAnn)
    End Sub

    Private Sub RemoveLastAnnotationCreationPoint(ByVal objAnn As Annotation)
        If objAnn.Points.Count > 0 Then
            objAnn.Points.RemoveAt(objAnn.Points.Count - 1)

            'Add removed point later
            RaiseEvent AnnotationCreationPointRemoved(objAnn)
        End If
    End Sub

    Private Sub AddAnnotationCreationPoint(ByVal objClientPt As Point, _
                                           ByVal objAnn As Annotation, _
                                           ByVal bRequestEnd As Boolean)
        'Add an annotation point and redraw appropriate area
        'Adding is not performed if the new point is equal to the last one!
        Dim objImagePt As Point = ClientToImageCoordinates(objClientPt, Me.sDisplayZoom, Me.ptDisplayOffset)

        If objAnn.Points.Count > 0 Then
            If objImagePt.Equals(objAnn.Points(objAnn.Points.Count - 1)) Then
                Exit Sub
            End If
        End If

        objAnn.Points.Add(objImagePt)
        Debug.WriteLine("Adding annotation point at " & objImagePt.X & ", " & objImagePt.Y, _
                        "Annotations.AddAnnotationCreationPoint")
        RaiseEvent AnnotationCreationPointAdded(objAnn)

        If CanOrMustExitAnnotationCreation(objAnn, bRequestEnd) Then
            ExitAnnotationCreation(objAnn)
        End If
    End Sub


#End Region

#Region "GUI"

    Friend Sub SetAnnotationProperties(ByVal oAnn As Annotation)
        Dim oAnnProps As New AnnotationProperties
        oAnnProps.SetDialogProperties(oAnn)

        If oAnnProps.ShowDialog() = System.Windows.Forms.DialogResult.OK Then
            oAnnProps.SetAnnotationProperties(oAnn)
            Me.Draw()
        End If
    End Sub

    Friend Sub SetAnnotationContainerProperties(ByVal oAnn As AnnotationContainer)
        Dim oAnnConProps As New AnnotationContainerProperties
        oAnnConProps.SetDialogProperties(oAnn)

        If oAnnConProps.ShowDialog() = DialogResult.OK Then
            oAnnConProps.SetAnnotationProperties(oAnn)
            Me.Draw()
        End If
    End Sub

    Friend Sub CreateNewAnnotationContainer()
        Dim oAnnConProps As New AnnotationContainerProperties
        oAnnConProps.SetDialogNewProperties()
        If oAnnConProps.ShowDialog() = DialogResult.OK Then
            oAnnConProps.SetAnnotationProperties(Me.oAnnotationContainers)
            Me.Draw()
        End If
    End Sub

    Friend Sub CreateNewAnnotation(ByVal oAnnContainer As AnnotationContainer)
        Dim oAnnProps As New AnnotationProperties
        oAnnProps.SetDialogNewProperties(oAnnContainer)

        If oAnnProps.ShowDialog() = DialogResult.OK Then
            'Do interactive creation
            Me.CreateAnnotation(oAnnProps.SetAnnotationProperties(oAnnContainer))
            Me.Draw()
        End If
    End Sub

    Friend Sub CreateNewAnnotationInDefaultContainer()
        'Find out if the container 'Default' already exists ...
        Dim oAnnContainer As AnnotationContainer
        If Me.oAnnotationContainers.KeyExists("Default") Then
            oAnnContainer = Me.oAnnotationContainers("Default")
        Else
            oAnnContainer = New AnnotationContainer
            oAnnContainer.Color = Color.FromArgb(64, Color.Red)
            Me.oAnnotationContainers.Add(oAnnContainer, "Default")
        End If
        CreateNewAnnotation(oAnnContainer)
    End Sub

    Friend Sub DeleteAnnotation(ByVal oForm As Form, ByVal oAnn As Annotation)
        'Allow delete if clicked, after confirmation!
        If CBool(CommonNamespace.SkippableMessage.Show(oForm, "Deleteannotation", "Deleting ROI", "Are you sure you want to delete the selected ROI?", True, True)) Then
            Debug.WriteLine("Removing annotation with key " & oAnn.Key, "menuDeleteAnnotation_Click")
            oAnn.AnnotationContainer.Annotations.Remove(oAnn.Key)
        End If
    End Sub

    Friend Sub DeleteAnnotationContainer(ByVal oForm As Form, ByVal oAnnContainer As AnnotationContainer)
        If CommonNamespace.SkippableMessage.Show(oForm, "DeleteAnnotationContainer", "Deleting ROI GROUP", "Are you sure you want to delete the ROI GROUP " & oAnnContainer.Key & "?", True, True) = DialogResult.OK Then
            Me.oAnnotationContainers.Remove(oAnnContainer.Key)
            Me.Draw()
        End If
    End Sub

    Friend Sub DeleteAllAnnotationContainers(ByVal oForm As Form)
        If CommonNamespace.SkippableMessage.Show(oForm, "DeleteAnnotationContainer", "Deleting ROI GROUPS", "Are you sure you want to delete all the ROI GROUPS?", True, True) = DialogResult.OK Then
            Me.oAnnotationContainers.Clear()
            Me.Draw()
        End If
    End Sub

    Private Function DisplayImageBiggerThanCanvas() As Boolean
        If Me.MaximimumSize.Width < Me.DisplaySize.Width Or Me.MaximimumSize.Height < Me.DisplaySize.Height Then
            Return True
        Else
            Return False
        End If
    End Function

    Private Function ComputeInitialDisplayZoom() As Single
        'Compute the best startDisplayZoom. Also set the scrollbars to 0 
        If Me.BitmapPresent Then
            Dim sHorDisplayZoom As Single = MaximimumSize.Height / CSng(oBitmap.Height())
            Dim sVertDisplayZoom As Single = MaximimumSize.Width / CSng(oBitmap.Width())
            Return Math.Min(sHorDisplayZoom, sVertDisplayZoom)
        Else
            Return 1
        End If
    End Function

    Private Sub SizeControl()
        'We take care of sizing ourselves, based on the DisplayZoom
        'We cannot exceed the screensize MaximimumSize
        'Setting size in one call causes less events in parent!
        'We must be sure a resize event gets triggered because that will cause the 
        'redraw when loading a new image!
        Dim objSize As New System.Drawing.Size(799, 599)
        If Me.BitmapPresent Then
            'Compute size of control
            Me.SuspendLayout()
            Dim iImageWidth As Integer = CInt(Me.oBitmap.Width() * Me.DisplayZoom)
            Dim iImageHeight As Integer = CInt(Me.oBitmap.Height() * Me.DisplayZoom)

            If iImageWidth > Me.MaximimumSize.Width Then
                objSize.Width = Me.MaximimumSize.Width
            Else
                objSize.Width = iImageWidth
            End If

            If iImageHeight > Me.MaximimumSize.Height Then
                objSize.Height = Me.MaximimumSize.Height
            Else
                objSize.Height = iImageHeight
            End If
        End If
        Debug.WriteLine("Control size set to " & objSize.ToString(), "AnnotatedImage.SizeControl")
        Me.Size = objSize
        Me.ResumeLayout()
        Me.Dirty = True
        RefreshTopForm()
    End Sub

    Private Sub RefreshTopForm()
        'Redraw the whole shebang ...
        'This avoids having to call refresh in higher level controls: all relevant redraws for image and annotations 
        'is handled here!
        Dim oTop As Windows.Forms.Control = Me
        Do While Not oTop.Parent Is Nothing
            oTop = oTop.Parent
        Loop
        oTop.Refresh()
    End Sub

    Private Sub CheckAndAdjustBitmapRectangle(ByRef objRect As Rectangle)
        If objRect.X < 0 Then
            objRect.Width += objRect.X
            objRect.X = 0
        End If
        If objRect.Y < 0 Then
            objRect.Height += objRect.Y
            objRect.Y = 0
        End If

        Dim fZoom As Single = Me.DisplayZoom
        Dim objBitmap As Bitmap = Me.Bitmap
        If objRect.Bottom >= (objBitmap.Height * fZoom) Then
            objRect.Height = CInt(Math.Round((objBitmap.Height - objRect.Y - 1) * fZoom))
        End If

        If (objRect.Right >= (objBitmap.Width * fZoom)) Then
            objRect.Width = CInt(Math.Round((objBitmap.Width - objRect.X - 1) * fZoom))
        End If
    End Sub
#End Region

#Region "Misc"

    Public Sub RaiseBitmapDataChangedEvent()
        'It is not possible to determine if the data of a bitmap has changed, so this event must be 
        'raised manually ....
        Debug.WriteLine("Manually raising BitmapDataChanged event", "RaiseBitmapDataChangedEvent")
        RaiseEvent BitmapDataChanged(Me.oBitmap)
    End Sub

    Public Sub RaiseChangedEvent(ByVal sender As Object)
        If Not Me.bUpdating Then
            'Draw also checks for updating but this cannot be avoided
            RaiseEvent AnnotationsChanged(sender)
            Me.Draw()
        End If
    End Sub

    Public Function GetArea(ByVal oRgn As Region) As Single
        'Needs the bitmap for bounds.
        Dim x, y, iNrPixels As Integer
        Dim xMax, yMax As Integer

        If Me.BitmapPresent Then
            xMax = Me.ImageSize.Width
            yMax = Me.ImageSize.Height

            iNrPixels = 0
            For x = 0 To xMax
                For y = 0 To yMax
                    If oRgn.IsVisible(x, y) Then iNrPixels += 1
                Next
            Next
            Return iNrPixels
        Else
            Return 0
        End If
    End Function

    Public Sub ShowRegions(ByVal bShow As Boolean)
        Dim objAnnotationContainer As AnnotationContainer

        Debug.WriteLine("Showing Annotations regions", "RegionVisibles")
        For Each objAnnotationContainer In Me.oAnnotationContainers
            objAnnotationContainer.Color = Color.FromArgb(128, objAnnotationContainer.Color)
        Next
    End Sub

    Public Function GetAnnotation(ByVal objPt As Point) As Annotation
        Dim objAnnotationContainer As AnnotationContainer, objAnnotation As Annotation = Nothing
        For Each objAnnotationContainer In Me.oAnnotationContainers
            objAnnotation = objAnnotationContainer.GetAnnotation(objPt)
            If Not objAnnotation Is Nothing Then
                Exit For
            End If
        Next
        'Debug.WriteLine("BorderHittest for AnnotationContainerset  at " & objPt.X & ", " & objPt.Y & ": " & bHit)
        Return objAnnotation
    End Function

    Public Function GetAnnotationContainer(ByVal objPt As Point) As Object
        Dim oAnnContainer As AnnotationContainer
        Dim oResult As AnnotationContainer = Nothing
        For Each oAnnContainer In Me.oAnnotationContainers
            If oAnnContainer.IsInside(objPt) Then
                oResult = oAnnContainer
                Exit For
            End If
        Next
        'Debug.WriteLine("BorderHittest for AnnotationContainerset  at " & objPt.X & ", " & objPt.Y & ": " & bHit)
        Return oResult
    End Function

#End Region

#End Region

#Region "Event handlers"

    Public Sub AnnotationContainers_ChangedEvent(ByVal sender As Object) Handles oAnnotationContainers.Changed
        RaiseChangedEvent(sender)
    End Sub


    Private Sub AnnotatedImage_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles MyBase.KeyDown
        If Not Me.BitmapPresent Then Exit Sub
        Debug.WriteLine("Pressed key " & e.KeyCode, "AnnotatedImage.KeyDown")

        If e.Shift Then
            Select Case e.KeyCode
                Case Keys.Z
                    'Zoom in
                    Me.DisplayZoom = CSng(Me.DisplayZoom * 1.25)
                Case Keys.W
                    'Decrease averaging window size
                    If PixelAveragingWindowSize > 1 Then
                        PixelAveragingWindowSize = PixelAveragingWindowSize - 2
                    End If
            End Select
        ElseIf e.Alt Then
        ElseIf e.Control Then
            If Me.bCreatingAnnotation Then
                If e.KeyCode = Keys.Z Then
                    'Remove last added point in annotation creation  mode
                    Dim objAnn As Annotation = Me.AnnotationContainers(Me.strCreatedAnnotationAnnotationContainerKey).Annotations(Me.strCreatedAnnotationKey)
                    RemoveLastAnnotationCreationPoint(objAnn)
                End If
            End If
        Else
            Select Case e.KeyCode
                Case Keys.Z
                    'Zoom out
                    Me.DisplayZoom = CSng(Me.DisplayZoom / 1.25)
                Case Keys.W
                    'Increase averaging window size
                    PixelAveragingWindowSize = PixelAveragingWindowSize + 2
                Case Keys.F5
                    'Cause refresh of client area
                    Me.Draw()
                Case Keys.Delete
                    'Delete selected annotation
                    If Not Me.bCreatingAnnotation Then
                        Dim objClientPt As Point = Me.PointToClient(Control.MousePosition)
                        Dim objItem As Annotation = Me.GetAnnotation(ClientToImageCoordinates(objClientPt, Me.sDisplayZoom, Me.ptDisplayOffset))
                        If Not objItem Is Nothing Then
                            DeleteAnnotation(Nothing, objItem)
                        End If
                    End If
                Case Keys.Escape
                    If Me.bCreatingAnnotation Then
                        Dim objAnn As Annotation = Me.AnnotationContainers(Me.strCreatedAnnotationAnnotationContainerKey).Annotations(Me.strCreatedAnnotationKey)
                        CancelAnnotationCreation(objAnn)
                    End If

                Case Keys.Enter
                    If Me.bCreatingAnnotation Then
                        'End annotation creation if permitted
                        Dim objAnn As Annotation = Me.AnnotationContainers(Me.strCreatedAnnotationAnnotationContainerKey).Annotations(Me.strCreatedAnnotationKey)
                        If CanOrMustExitAnnotationCreation(objAnn, True) Then ExitAnnotationCreation(objAnn)
                    End If
                Case Keys.Back
                    If Me.bCreatingAnnotation Then
                        Dim objAnn As Annotation = Me.AnnotationContainers(Me.strCreatedAnnotationAnnotationContainerKey).Annotations(Me.strCreatedAnnotationKey)
                        RemoveLastAnnotationCreationPoint(objAnn)
                    End If
            End Select
        End If
    End Sub

    Private Sub AnnotatedImage_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseDown
        If Not Me.BitmapPresent Then Exit Sub

        'One can always move the image. This is always with the middle mouse button
        Debug.WriteLine("Mouse button " & e.Button & " at " & e.X & "," & e.Y, "AnnotatedImage.MouseDown")
        Dim objClientPt As New Point(e.X, e.Y)
        Select Case e.Button
            Case Forms.MouseButtons.Middle
                If DisplayImageBiggerThanCanvas() Then
                    Me.Cursor = System.Windows.Forms.Cursors.SizeAll
                    objMousePos.X = e.X
                    objMousePos.Y = e.Y
                End If
            Case Forms.MouseButtons.Left
                If Me.bCreatingAnnotation Then
                    'Insert point in annotation and redraw
                    Dim objAnn As Annotation = Me.AnnotationContainers(Me.strCreatedAnnotationAnnotationContainerKey).Annotations(Me.strCreatedAnnotationKey)

                    'Double click to end annotation creation creates problems. Use Menu
                    'Dim bRequestEnd As Boolean = False
                    'If e.Clicks > 1 Then
                    '  bRequestEnd = True
                    'End If
                    AddAnnotationCreationPoint(objClientPt, objAnn, False)
                Else
                    'Select annotation
                    Dim oImgPt As Point = ClientToImageCoordinates(objClientPt, Me.sDisplayZoom, Me.ptDisplayOffset)
                    Dim objItem As Object = Me.GetAnnotation(oImgPt)
                    'Select/Deselect

                    If objItem Is Nothing Then
                        'Maybe we hit a container
                        objItem = Me.GetAnnotationContainer(oImgPt)
                    End If

                    Me.SelectedItem = objItem
                End If

            Case Forms.MouseButtons.Right
                'Show context sensitive menu

                If Me.bCreatingAnnotation Then
                    'Show popup menu with options available while creating an annotation: Cancel, Done, Undo.
                    If Me.oCreatingAnnotationMenu Is Nothing Then
                        Me.oCreatingAnnotationMenu = New ContextMenu
                        Me.oCreatingAnnotationMenuItemDone = New MenuItem("Done (Enter)")
                        Me.oCreatingAnnotationMenuItemCancel = New MenuItem("Cancel (Esc)")
                        Me.oCreatingAnnotationMenuItemUndo = New MenuItem("Undo (Ctrl-z)")

                        With Me.oCreatingAnnotationMenu
                            .MenuItems.Add(Me.oCreatingAnnotationMenuItemDone)
                            .MenuItems.Add(Me.oCreatingAnnotationMenuItemCancel)
                            .MenuItems.Add(Me.oCreatingAnnotationMenuItemUndo)
                        End With
                    End If

                    Me.oCreatingAnnotationMenu.Show(Me, objClientPt)
                Else
                    'Show popup menu with options available while creating an annotation: Cancel, Done, Undo.
                    Dim objItem As Annotation = Me.GetAnnotation(ClientToImageCoordinates(objClientPt, Me.sDisplayZoom, Me.ptDisplayOffset))
                    If Not objItem Is Nothing Then
                        'Select annotation. This will be used to identify the annotation that caused the popup
                        Me.SelectedItem = objItem

                        If Me.oAnnotationMenu Is Nothing Then
                            Me.oAnnotationMenu = New ContextMenu
                            Me.oAnnotationMenuItemAdd = New MenuItem("New ROI in same ROI-GROUP")
                            Me.oAnnotationMenuItemDelete = New MenuItem("Delete")
                            Me.oAnnotationMenuItemDeleteContainer = New MenuItem("Delete ROI-GROUP")
                            Me.oAnnotationMenuItemProperties = New MenuItem("Properties")
                            With Me.oAnnotationMenu
                                .MenuItems.Add(Me.oAnnotationMenuItemAdd)
                                .MenuItems.Add(Me.oAnnotationMenuItemProperties)
                                .MenuItems.Add(Me.oAnnotationMenuItemDelete)
                                .MenuItems.Add(Me.oAnnotationMenuItemDeleteContainer)
                            End With
                        End If

                        Me.oAnnotationMenu.Show(Me, objClientPt)

                    End If
                End If
        End Select
    End Sub

    Private Sub AnnotatedImage_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseMove
        If Not Me.BitmapPresent Then Exit Sub

        If e.Button = Forms.MouseButtons.Right Then
            If DisplayImageBiggerThanCanvas() Then
                'Adjust DisplayOffset
                Dim objCurrentOffset As Point = Me.DisplayOffset
                Me.DisplayOffset = New Point(CInt(objCurrentOffset.X + (objMousePos.X - e.X) / Me.sDisplayZoom), _
                                             CInt(objCurrentOffset.Y + (objMousePos.Y - e.Y) / Me.sDisplayZoom))
                objMousePos.X = e.X
                objMousePos.Y = e.Y
            End If
        Else
            Dim objClientPt As New Point(e.X, e.Y)

            If Me.bCreatingAnnotation Then
                'Set cursor
                Me.Cursor = Cursors.Cross

                'Get annotation being created 
                Dim objAnn As Annotation = Me.AnnotationContainers(Me.strCreatedAnnotationAnnotationContainerKey).Annotations(Me.strCreatedAnnotationKey)

                'If button is down we just add points, else we draw a reversible line
                If e.Button = Forms.MouseButtons.Left Then
                    AddAnnotationCreationPoint(objClientPt, objAnn, False)
                Else
                    If objAnn.Visible And objAnn.LineWidth > 0 And objAnn.Points.Count > 0 Then
                        'Some points have already been created, draw an erasable line from last to current 
                        'location. Annotations are in image coordinates! 
#If ALLEVENTS Then
          Debug.WriteLine("Drawing erasable line", "Annotations.MouseMove")
#End If

                        Dim objLastAnnPt As Point = objAnn.Points.Item(objAnn.Points.Count - 1)
                        Me.DrawErasableLine(ImageToClientCoordinates(objLastAnnPt, Me.sDisplayZoom, Me.ptDisplayOffset), objClientPt)
                    End If
                End If

                'Raise event in parent
                RaiseEvent AnnotationCreationMouseMove(e, objAnn)
            Else
                'Check if we hit an annotation
                Dim objItem As Annotation = Me.GetAnnotation(ClientToImageCoordinates(objClientPt, Me.sDisplayZoom, Me.ptDisplayOffset))
                If Not objItem Is Nothing Then
                    'Change cursor, and raise event
                    Me.Cursor = Cursors.Hand
                    RaiseEvent AnnotationHover(e, objItem)
                Else
                    Me.Cursor = Cursors.Arrow
                End If

            End If
        End If
    End Sub

    Private Sub AnnotatedImage_MouseUp(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseUp
        If Not Me.BitmapPresent Then Exit Sub
        If e.Button = Forms.MouseButtons.Right Then
            Me.Cursor = System.Windows.Forms.Cursors.Default
        End If
    End Sub

    Private Sub AnnotatedImage_MouseWheel(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseWheel
        Debug.WriteLine("Mouse wheel Delta " & e.Delta, "AnnotatedImage.MouseWheel")
        If e.Delta > 0 Then
            Me.DisplayZoom = CSng(Me.DisplayZoom / 1.25)
        Else
            Me.DisplayZoom = CSng(Me.DisplayZoom * 1.25)
        End If
    End Sub

    Private Sub AnnotatedImage_DoubleClick(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.DoubleClick
        'Show annotation manager if noton annotation, else show property dialog.
        'Do not show when creating an annotation
        If Not Me.bCreatingAnnotation Then
            Dim objClientPt As Point = Me.PointToClient(Control.MousePosition)
            Dim objItem As Annotation = Me.GetAnnotation(ClientToImageCoordinates(objClientPt, Me.sDisplayZoom, Me.ptDisplayOffset))
            If Not objItem Is Nothing Then
                Me.SetAnnotationProperties(objItem)
            Else
                'Me.ShowAnnotationsManager(True) WHOLE series of problems!
            End If
        End If
    End Sub

    Private Sub AnnotationMenuAdd_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles oAnnotationMenuItemAdd.Click
        'Handles clicking in AnnotationMenu
        Dim oAnn As Annotation = CType(Me.oSelectedItem, Annotation)
        Me.CreateNewAnnotation(oAnn.AnnotationContainer)
    End Sub

    Private Sub AnnotationMenuDelete_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles oAnnotationMenuItemDelete.Click
        'Handles clicking in AnnotationMenu
        Me.DeleteAnnotation(Nothing, CType(Me.oSelectedItem, Annotation))
    End Sub

    Private Sub AnnotationMenuProperties_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles oAnnotationMenuItemProperties.Click
        'Handles clicking in AnnotationMenu
        Me.SetAnnotationProperties(CType(Me.oSelectedItem, Annotation))
    End Sub

    Private Sub AnnotationMenuDeleteContainer_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles oAnnotationMenuItemDeleteContainer.Click
        'Handles clicking in AnnotationMenu
        Dim oAnn As Annotation = CType(Me.oSelectedItem, Annotation)
        Me.DeleteAnnotationContainer(Nothing, oAnn.AnnotationContainer)
    End Sub

    Private Sub AnnotationsManager_VisibleChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles oAnnotationsManager.VisibleChanged
        RaiseEvent AnnotationsManagerVisibleChanged(Me.oAnnotationsManager.Visible)
    End Sub

    Private Sub AnnotationCreationMenuDone_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles oCreatingAnnotationMenuItemDone.Click
        'Handles clicking in AnnotationCreationmenu
        Dim objAnn As Annotation = Me.AnnotationContainers(Me.strCreatedAnnotationAnnotationContainerKey).Annotations(Me.strCreatedAnnotationKey)
        If CanOrMustExitAnnotationCreation(objAnn, True) Then ExitAnnotationCreation(objAnn)
    End Sub

    Private Sub AnnotationCreationMenuCancel_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles oCreatingAnnotationMenuItemCancel.Click
        'Handles clicking in AnnotationCreationmenu
        Dim objAnn As Annotation = Me.AnnotationContainers(Me.strCreatedAnnotationAnnotationContainerKey).Annotations(Me.strCreatedAnnotationKey)
        CancelAnnotationCreation(objAnn)
    End Sub

    Private Sub AnnotationCreationMenuUndo_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles oCreatingAnnotationMenuItemUndo.Click
        'Handles clicking in AnnotationCreationmenu
        Dim objAnn As Annotation = Me.AnnotationContainers(Me.strCreatedAnnotationAnnotationContainerKey).Annotations(Me.strCreatedAnnotationKey)
        RemoveLastAnnotationCreationPoint(objAnn)
    End Sub

#End Region

    Public Class AnnotationContainer
        Protected WithEvents objAnnotations As New AnnotationCollection(Me)
        Private strText As String, bRegionVisible, bNeedRegionUpdate, bVisible, bTextVisible As Boolean, oColor As Color
        Friend oAnnotatedImage As AnnotatedImage, strKey As String
        Protected objRegion As New Region

        'Public Events
        Public Event RegionDrawn(ByVal region As Region)
        Public Event Changed(ByVal sender As Object)

        Private Sub Annotations_Changed(ByVal sender As Object) Handles objAnnotations.Changed
            If sender Is Nothing Then sender = Me
            Me.bNeedRegionUpdate = True
            RaiseEvent Changed(sender)
        End Sub

#Region "Constructor"

        Public Sub New()
            Me.new(System.Drawing.Color.White)
        End Sub

        Public Sub New(ByVal objColor As System.Drawing.Color)
            'We store the key used to store this AnnotationContainer in the AnnotationContainerset
            Debug.WriteLine("Creating new AnnotationContainer", "AnnotationContainer.New")
            Me.bRegionVisible = False
            Me.bVisible = True
            Me.objRegion.MakeEmpty()
            Me.oColor = objColor
        End Sub

        Protected Overrides Sub Finalize()
            'We clean up, the program leaks memory otherwise
            Me.objRegion.Dispose()
            MyBase.Finalize()
            Debug.WriteLine("Cleanup of " & Me.Key, "AnnotationContainer.Finalize")
        End Sub

#End Region

#Region "Properties"

        Public ReadOnly Property Annotations() As AnnotationCollection
            'Return all annotations inside container
            Get
                Return Me.objAnnotations
            End Get
        End Property

        Public ReadOnly Property AnnotatedImage() As AnnotatedImage
            'Return the parent object. Because we have used the name annotations for the collection of annotations
            'we have used another name ....
            Get
                Return Me.oAnnotatedImage
            End Get
        End Property

        Public Property Visible() As Boolean
            Get
                Return Me.bVisible
            End Get
            Set(ByVal Value As Boolean)
                Me.bVisible = Value
                RaiseEvent Changed(Me)
            End Set
        End Property

        Public ReadOnly Property Key() As String
            Get
                Return Me.strKey
            End Get
        End Property

        Public Property Text() As String
            Get
                Return Me.strText
            End Get
            Set(ByVal Value As String)
                Me.strText = Value
                RaiseEvent Changed(Me)
            End Set
        End Property

        Public Property Color() As Color
            'Used as default color for child objects, and as fill color if opacity is > 0 
            'Borders are always fully opaque
            Get
                Return Me.oColor
            End Get
            Set(ByVal Value As Color)
                Me.oColor = Value
                RaiseEvent Changed(Me)
            End Set
        End Property

        Friend ReadOnly Property Region() As Region
            'Returns a reference to the internal region for internal use only!!!!
            'Never modify it
            Get
                Updateregion()
                Return Me.objRegion
            End Get
        End Property

#End Region

#Region "Methods"

        Public Function GetArea() As Single
            'Compute the area of closed annotation in pixel units. This is 
            'approximative, a small error may become apparent for region with few pixels 
            'because border pixels are counted completely
            'Due to the ineffcient implementation of IsVisible it will also be slow
            Return Me.oAnnotatedImage.GetArea(Me.GetRegion)
        End Function

        Public Function GetRegion() As Region
            'Transform all annotations in the AnnotationContainer to one region, using the RegionAddMode
            'of each annotation
            Me.Updateregion()
            Return Me.objRegion.Clone()
        End Function

        Private Sub CombineRegions(ByVal objCombinedRgn As Region, ByVal objRgn As Region, ByVal iRegionAddMode As RegionAddMode)
            'Combine region with given region using the given mode. Used to combine annotations in a container to
            'one region
            Select Case iRegionAddMode
                Case RegionAddMode.Complement
                    objCombinedRgn.Complement(objRgn)
                Case RegionAddMode.Intersect
                    objCombinedRgn.Intersect(objRgn)
                Case RegionAddMode.Union
                    objCombinedRgn.Union(objRgn)
                Case RegionAddMode.XorMode
                    objCombinedRgn.Xor(objRgn)
            End Select

        End Sub

        Friend Function GetAnnotation(ByVal objPt As Point) As Annotation
            Dim objAnnotation As Annotation
            For Each objAnnotation In Me.objAnnotations
                If objAnnotation.IsOnBorder(objPt) = True Then
                    Return objAnnotation
                End If
            Next
            Return Nothing
        End Function

        Public Function IsInside(ByVal oPt As Point) As Boolean
            'Check whether point is inside closed annotation
            Me.Updateregion()
            Return Me.objRegion.IsVisible(oPt.X, oPt.Y)
        End Function

        Public Function IsSelected() As Boolean
            Dim bSelected As Boolean = False
            If Not Me.StandAlone() Then
                If Object.Equals(Me, Me.oAnnotatedImage.SelectedItem) Then
                    bSelected = True
                End If
            End If
            Return bSelected
        End Function

        Private Sub Updateregion()
            If Me.bNeedRegionUpdate Then
                Dim objAnnotation As Annotation
                Me.objRegion.MakeEmpty()
                For Each objAnnotation In Me.objAnnotations
                    CombineRegions(objRegion, objAnnotation.Region, objAnnotation.RegionAddMode)
                Next
                Me.bNeedRegionUpdate = False
            End If
        End Sub

        Friend Sub Draw(ByVal g As Graphics, ByVal m As Matrix)
            'Draw the AnnotationContainer, after the background image has been repainted
            'GetRegion is an expensive step, avoid for faster drawing!!!!
            Debug.WriteLine("Drawing AnnotationContainer " & Me.Key, "AnnotationContainer.Draw")

            If Me.bVisible = True Then
                Dim bSelected As Boolean = Me.IsSelected()
                Dim objAnnotation As Annotation


                For Each objAnnotation In Me.objAnnotations
                    objAnnotation.Draw(g, m)
                Next

                'If container selected show in white points
                'Show the region in desired color. Avoid coloring regions for fast drawing!!!!
                If Me.oColor.A > 0 Then
                    'Show the region in desired color. Avoid coloring regions for fast drawing!!!!
                    Dim objRegion As Region
                    Dim objBrush As New System.Drawing.SolidBrush(Me.oColor)
                    objRegion = Me.GetRegion
                    If Not objRegion.IsEmpty(g) Then
                        objRegion.Transform(m)
                        g.FillRegion(objBrush, objRegion)
                    End If

                    'Cleanup
                    objRegion.Dispose()
                    objBrush.Dispose()
                End If

                If bSelected Then
                    Dim objRegion As Region
                    Dim objBrush As New Drawing2D.HatchBrush(HatchStyle.Percent20, Drawing.Color.White, Drawing.Color.Transparent)
                    objRegion = Me.GetRegion
                    If Not objRegion.IsEmpty(g) Then
                        objRegion.Transform(m)
                        g.FillRegion(objBrush, objRegion)
                    End If

                    'Cleanup
                    objRegion.Dispose()
                    objBrush.Dispose()
                End If

                'Use region color, but without opacity!!!
                'Use location of first point in first annotation to avoid calling getRegion
                If Not Me.strText Is Nothing Then
                    If Me.strText.Length > 0 Then
                        If Me.Annotations.Count > 0 Then
                            If Me.Annotations(0).Points.Count > 0 Then
                                Dim oPt(0) As Point
                                oPt(0) = Me.Annotations(0).Points(0)
                                m.TransformPoints(oPt)
                                Dim drawFont As New Font("Verdana", 8, System.Drawing.FontStyle.Regular)
                                Dim drawBrush As New SolidBrush(Drawing.Color.FromArgb(Me.Color.R, Me.Color.G, Me.Color.B))
                                g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias
                                g.DrawString(strText, drawFont, drawBrush, oPt(0).X, oPt(0).Y)
                                drawFont.Dispose()
                                drawBrush.Dispose()
                            End If

                        End If
                    End If
                End If
            End If
        End Sub

        Private Function StandAlone() As Boolean
            'Return true if object not part of annotations object
            Dim bStandAlone As Boolean = True
            If Not Me.oAnnotatedImage Is Nothing Then bStandAlone = False
            Return bStandAlone
        End Function
#End Region

    End Class

    Public Class Annotation
        Private objColor As System.Drawing.Color
        Private bVisible, bClosed As Boolean
        Private strText As String, iRegionAddMode As RegionAddMode
        Protected objRegion As New Region, objBorderRegion As New Region
        Private bNeedRegionUpdate, bNeedBorderRegionUpdate As Boolean
        Private iMaximumNumberOfPoints, iPointSize, iLineWidth As Integer
        Protected WithEvents objPoints As New AnnotationPoints
        Friend oAnnotationContainer As AnnotationContainer, strKey As String
        Public PassKey As String = ""
        'Public Events
        Public Event Changed(ByVal sender As Object)

        Private Sub objPoints_Changed() Handles objPoints.Changed
            Me.bNeedBorderRegionUpdate = True
            Me.bNeedRegionUpdate = True
            RaiseEvent Changed(Me)
        End Sub

        ' Constructors
        Public Sub New()
            Me.new(False, 3, 1)
        End Sub

        Public Sub New(ByVal bClosed As Boolean)
            Me.new(bClosed, 3, 1)
        End Sub

        Public Sub New(ByVal bClosed As Boolean, _
                       ByVal iPointsSize As Integer, _
                       ByVal iLineWidth As Integer)
            'Object constructor
            MyBase.new()
            Debug.WriteLine("Annotation.New")
            Me.objColor = System.Drawing.Color.White 'It gets the color of the container when added to it if not set before adding
            Me.bVisible = True
            Me.iRegionAddMode = RegionAddMode.XorMode
            Me.objRegion.MakeEmpty()
            Me.iPointSize = iPointSize
            Me.iLineWidth = iLineWidth

            Me.iMaximumNumberOfPoints = -1 'Setting this to a positive value causes automatic stopping
            'of annotation creation at the prescribed number of points
            Me.bNeedRegionUpdate = True
            Me.bNeedBorderRegionUpdate = True
            Me.objRegion.MakeEmpty()
            Me.bClosed = bClosed
        End Sub

        '----------------------- Properties --------------------------

        Public ReadOnly Property Key() As String
            Get
                Return Me.strKey
            End Get
        End Property

        Public Property RegionAddMode() As RegionAddMode
            Get
                Return Me.iRegionAddMode
            End Get
            Set(ByVal Value As RegionAddMode)
                Me.iRegionAddMode = Value
                RaiseEvent Changed(Me)
            End Set
        End Property

        Public Property Color() As System.Drawing.Color
            Get
                Return Me.objColor
            End Get
            Set(ByVal Value As System.Drawing.Color)
                Me.objColor = Drawing.Color.FromArgb(Value.R, Value.G, Value.B)
                'Me.Draw()
                RaiseEvent Changed(Me)
            End Set
        End Property

        Public Property Visible() As Boolean
            Get
                Return Me.bVisible
            End Get
            Set(ByVal Value As Boolean)
                Me.bVisible = Value
                'Me.Draw()
                RaiseEvent Changed(Me)
            End Set
        End Property

        Public Property Text() As String
            Get
                Return Me.strText
            End Get
            Set(ByVal Value As String)
                Me.strText = Value
                'Me.Draw()
                RaiseEvent Changed(Me)
            End Set
        End Property

        Public Property Closed() As Boolean
            'Do the points represent a closed area, and thus an interior region?
            Get
                Return Me.bClosed
            End Get
            Set(ByVal Value As Boolean)
                Me.bClosed = Value
                Me.bNeedRegionUpdate = True
            End Set
        End Property

        Public Property MaximumNumberOfPoints() As Integer
            'Maximum number of point to be added interactively
            Get
                Return Me.iMaximumNumberOfPoints
            End Get
            Set(ByVal Value As Integer)
                Me.iMaximumNumberOfPoints = Value
            End Set
        End Property

        Public Property PointSize() As Integer
            'The size of the point marker
            Get
                Return Me.iPointSize
            End Get
            Set(ByVal Value As Integer)
                Me.iPointSize = Value
                'Me.Draw()
                RaiseEvent Changed(Me)
            End Set
        End Property

        Public Property LineWidth() As Integer
            Get
                Return Me.iLineWidth
            End Get
            Set(ByVal Value As Integer)
                Me.bNeedBorderRegionUpdate = True
                Me.iLineWidth = Value
                'Me.Draw()
                RaiseEvent Changed(Me)
            End Set
        End Property

        Public Property Points() As AnnotationPoints
            'Points are stored in a collection, but also in a graphicspath for drawing!
            Get
                Return Me.objPoints
            End Get
            Set(ByVal Value As AnnotationPoints)
                Me.objPoints = Value
                Me.bNeedBorderRegionUpdate = True
                Me.bNeedRegionUpdate = True
            End Set
        End Property

        Friend ReadOnly Property Region() As Region
            'Returns a reference to the internal region for internal use only!!!!
            'Never modify it
            Get
                UpdateRegion()
                Return Me.objRegion
            End Get
        End Property

        Public ReadOnly Property AnnotationContainer() As AnnotationContainer
            Get
                Return Me.oAnnotationContainer
            End Get
        End Property

        '-------------------------Methods-----------------------------

        Public Sub Rectangle(ByVal objRect As Rectangle)
            Me.objPoints.Rectangle(objRect)
            Me.bClosed = True
        End Sub

        Public Sub Circle(ByVal oCenter As Point, ByVal iRadius As Integer, ByVal iNrPts As Integer)
            Me.objPoints.Circle(oCenter, iRadius, iNrPts)
            Me.bClosed = True
        End Sub

        Public Function GetRegion() As Region
            'We do not want the user to mess up the region by passing on a reference!
            UpdateRegion()
            Return Me.objRegion.Clone
        End Function

        Public Function GetLength() As Single
            'Compute the length of annotation in pixel units
            Dim sLength As Single, i As Integer
            Dim oPts() As Point = CType(Me.objPoints.ToArray(Point.Empty.GetType), Point())

            For i = 1 To oPts.GetUpperBound(0)
                sLength = CSng(sLength + Math.Sqrt(Math.Pow(oPts(i).X - oPts(i - 1).X, 2) + Math.Pow(oPts(i).Y - oPts(i - 1).Y, 2)))
            Next

            If Me.bClosed Then
                'Add length last to first point!
                sLength = CSng(sLength + Math.Sqrt(Math.Pow(oPts(0).X - oPts(oPts.GetUpperBound(0)).X, 2) + Math.Pow(oPts(0).Y - oPts(oPts.GetUpperBound(0)).Y, 2)))
            End If
            Return sLength
        End Function

        Public Function GetArea() As Single
            'Compute the area of closed annotation in pixel units. This is 
            'approximative, a small error may become apparent for region with few pixels 
            'because border pixels are counted completely
            'Due to the ineffcient implementation of IsVisible it will also be slow
            Return Me.oAnnotationContainer.oAnnotatedImage.GetArea(Me.GetRegion)
        End Function

        Public Function IsInside(ByVal oPt As Point) As Boolean
            'Check whether point is inside closed annotation
            Me.UpdateRegion()
            Return Me.objRegion.IsVisible(oPt.X, oPt.Y)
        End Function

        Public Function IsOnBorder(ByVal oPt As Point) As Boolean
            'Check whether point is under path of annotation
            Me.UpdateBorderRegion()
            Return Me.objBorderRegion.IsVisible(oPt.X, oPt.Y)
        End Function

        Public Function IsSelected() As Boolean
            Dim bSelected As Boolean = False
            If Not Me.StandAlone() Then
                If Object.Equals(Me, Me.AnnotationContainer.oAnnotatedImage.SelectedItem) Then
                    bSelected = True
                End If
            End If
            Return bSelected
        End Function

        Friend Sub Draw(ByVal g As Graphics, ByVal m As Matrix)
            'Draw the annotation on the canvas
            'I do not like the way windows zooms because it makes overlay drawings ugly. 
            'I prefer to make overlays ALWAYS the same thickness and keep the font fixed, i.e. use screen coordinates
#If ALLEVENTS Then
      Debug.WriteLine("Drawing annotation " & Me.Key, "Annotation.Draw")
#End If

            If Me.Visible = True And Me.Points.Count > 0 Then
                Dim p As Pen = New Pen(Me.objColor, Me.iLineWidth)
                Dim pw As New Pen(System.Drawing.Color.White)
                Dim b As New SolidBrush(Me.objColor)

                'The currently selected object
                Dim bSelected As Boolean = Me.IsSelected

                'Draw lines and point markers. Creating a path turns out to be computationally expensive
                'so we try to draw directly when possible!
                Dim objPts() As Point = CType(Me.objPoints.ToArray(Point.Empty.GetType), Point())
                m.TransformPoints(objPts)

                'The lines
                If Me.iLineWidth > 0 And objPts.Length > 1 Then

                    If bSelected Then
                        'We must use a path to be able to widen it. This is quite a bit slower!
                        Dim gpLines As New GraphicsPath
                        gpLines.AddLines(objPts)
                        If Me.Closed Then
                            gpLines.AddLine(objPts(objPts.Length - 1), objPts(0))
                        End If
                        p.Width += 2
                        g.DrawPath(p, gpLines)
                        gpLines.Widen(p)
                        g.DrawPath(pw, gpLines)
                    Else
                        'Do direct drawing, without graphics path!
                        g.DrawLines(p, objPts)
                        If Me.Closed Then
                            g.DrawLine(p, objPts(objPts.Length - 1), objPts(0))
                        End If
                    End If
                    'Debug.WriteLine("Drawn " & Me.gpLines.PointCount - 1 & " lines for " & objPts.Length & " points", "Annotation.Draw")
                End If

                'The point markers
                If Me.iPointSize > 0 And objPts.Length > 0 Then
                    Dim iMarkerSize As Integer = Me.iPointSize
                    If bSelected Then iMarkerSize += 2
                    Dim oRect() As RectangleF = AnnotatedImage.PointToRectangle(objPts, iMarkerSize)
                    g.FillRectangles(b, oRect)

                    If bSelected Then
                        g.DrawRectangles(pw, oRect)
                    End If
                    'Debug.WriteLine("Drawn " & oRect.Length & " markers for " & objPts.Length & " points", "Annotation.Draw")
                End If

                'Text
                Dim strText As String = Me.strText
                If Not strText Is Nothing And objPts.Length > 0 Then
                    Dim sSize As Single = 6
                    If bSelected Then sSize = 8
                    Dim drawFont As New Font("Verdana", sSize, System.Drawing.FontStyle.Bold)
                    g.DrawString(strText, drawFont, b, objPts(0).X, objPts(0).Y)
                End If

                'cleanup
                pw.Dispose()
                b.Dispose()
                p.Dispose()
            End If
        End Sub

        Private Sub UpdateBorderRegion()
            'Used for hit testing in image coordinates!!!!
            If Me.bNeedBorderRegionUpdate Then
#If ALLEVENTS Then
          Debug.WriteLine("Updating border region for annotation " & Me.strKey, "Annotation.UpdateRegion")
#End If

                'Based on the graphics paths with zoom 1 and offset (0,0)
                Me.objBorderRegion.MakeEmpty()

                Dim objPts() As Point = CType(Me.objPoints.ToArray(Point.Empty.GetType), Point())
                Dim objGraphPath As New System.Drawing.Drawing2D.GraphicsPath
                If objPts.Length > 1 Then
                    objGraphPath.AddLines(objPts)
                    objGraphPath.Widen(New Pen(System.Drawing.Color.White, Me.iLineWidth + 2))
                End If

                If Me.iPointSize > 0 And objPts.Length > 0 Then
                    objGraphPath.AddRectangles(AnnotatedImage.PointToRectangle(objPts, CSng(Me.iPointSize)))
                End If

                Me.objBorderRegion.Union(objGraphPath)
                Me.bNeedBorderRegionUpdate = False
            End If
        End Sub

        Private Sub UpdateRegion()
            'Transform to a region in image coordinates for the border annotation, and cache it 
            If Me.bNeedRegionUpdate Then
                If Me.Closed Then
#If ALLEVENTS Then
          Debug.WriteLine("Updating region for annotation " & Me.strKey, "Annotation.UpdateRegion")
#End If
                    Dim objPts() As Point = CType(Me.objPoints.ToArray(Point.Empty.GetType), Point())
                    If objPts.Length > 2 Then
                        Dim objGraphPath As New System.Drawing.Drawing2D.GraphicsPath
                        objGraphPath.AddPolygon(objPts)
                        Me.objRegion.MakeEmpty()
                        Me.objRegion.Union(objGraphPath)
                    End If
                Else
#If ALLEVENTS Then
          Debug.WriteLine("Annotation " & Me.Key & " is not closed and thus has no region!", "Annotation.UpdateRegion")
#End If
                    Me.objRegion.MakeEmpty() 'WE cannot test for emptyness first because this, &#^%Q&$, requires a Graphics
                End If
                Me.bNeedRegionUpdate = False
            End If
        End Sub

        Private Function StandAlone() As Boolean
            'Return true if object not part of annotations object
            Dim bStandAlone As Boolean = True
            If Not Me.oAnnotationContainer Is Nothing Then
                If Not Me.oAnnotationContainer.oAnnotatedImage Is Nothing Then
                    bStandAlone = False
                End If
            End If
            Return bStandAlone
        End Function

        Protected Overrides Sub Finalize()
            'We clean up, the program leaks memory otherwise due to unmanaged GDI+ objects
            Debug.WriteLine("Cleanup of annotation " & Me.strKey, "Annotation.Finalize")

            'Use private variables to avoid generating events
            Me.objRegion.Dispose()
            MyBase.Finalize()
        End Sub
    End Class

End Class
